If you're considering between using (abstract) classes or interfaces, both are viable options. However, when it comes to making the factory itself generic, it may not be the best approach. Instead, consider making the contract type generic. I experimented a bit and managed to piece together this solution:
interface Contract { }
interface Factory<A extends any[], C extends Contract> {
deploy: (...args: A) => C;
}
// Additional types for assistance
type FactoryArgs<F extends Factory<any, any>> = F extends Factory<infer A, any> ? A : never;
type FactoryContractType<F extends Factory<any, any>> = F extends Factory<any, infer C> ? C : never;
interface FactoryForClass<C extends new (...args: any) => Contract> {
deploy: Factory<ConstructorParameters<C>, InstanceType<C>>['deploy'];
}
class CustomContract implements Contract {
constructor(a: number, b: number) { }
}
class CustomFactory implements FactoryForClass<typeof CustomContract> {
deploy(x: number, y: number): CustomContract {
return new CustomContract(x, y);
}
}
type MockContract<C extends Contract> = Contract & C & {
mockProperty: number;
}
type MockFactory<F extends Factory<any, any>> = F & {
deploy: (...args: FactoryArgs<F>) => MockContract<FactoryContractType<F>>;
}
const mockFactory: MockFactory<CustomFactory> = {
deploy(a: number, b: number) {
const customContract = new CustomContract(a, b);
const result: CustomContract & MockContract<CustomContract> = customContract as any;
result.mockProperty = 123;
return result;
}
};