问题
For testing purpose, I'd like to be able to create an object implementing an interface, only with function I need for my test, without having to manually maintain a mock object with all possible properties. Generally, I'm using one function at a time, so don't need to define all others but I don't want TS to keep complaining about missing properties.
For example, I have an interface IFoo :
interface IFoo {
myFunc(): string;
otherFunc(): number;
}
I tried to create a mapped type, which assign jest.Mock<{}> to all properties of IFoo
type Mockify<T> = {
[P in keyof T]: jest.Mock<{}>
};
Calling it like this:
const mockFoo: Mockify<IFoo> = {
otherFunc: {
// Mocked function behavior
}
}
The problem with this approach is that TS complains about the missing myFunc property on the object passed to Mockify.
I also tried Mockify<Partial<IFoo>> to ignore missing properties, but then my type defintion is different from IFoo and some other functions depending of IFoo are complaining.
I could also define all properties as optionnal, but conceptually I don't like that.
I have the feeling that mapped types could make the job, but I maybe don't have the right approach.
Thanks for your help!
回答1:
I can make all properties optionnal in my mapped type definition:
type Mockify<T> = {
[P in keyof T]?: jest.Mock<{}>
};
This is equivalent to use Partial everytime I use Mockify.
Then, when using my mockified object later on, type assertion casts it back to the original interface and everybody's happy
const mockFoo: Mockify<IFoo> = {
otherFunc: {
// Mocked function behavior
}
}
dependentFunc(mockFoo as IFoo);
回答2:
If I understand correctly, you are trying to partial mock types. Here is the solution:
versions:
"jest": "^24.8.0",
"ts-jest": "^24.0.2",
"@types/jest": "^24.0.17",
"typescript": "^3.5.3"
Foo.ts:
interface IFoo {
myFunc(): string;
otherFunc(): number;
}
class Foo implements IFoo {
public myFunc(): string {
return 'myFunc';
}
public otherFunc(): number {
return 1;
}
}
export { Foo, IFoo };
Use Foo class in SomeClass as dependency:
import { IFoo } from './Foo';
interface ISomeClass {
say(): string;
}
interface ISomeClassOptions {
foo: IFoo;
}
class SomeClass implements ISomeClass {
private foo: IFoo;
constructor(options: ISomeClassOptions) {
this.foo = options.foo;
}
public say(): string {
return this.foo.myFunc();
}
}
export { SomeClass, ISomeClassOptions };
Unit test, we can partial mocked foo only with myFunc method use the mock helper function. It will handle the type issue of typescript.
import { SomeClass, ISomeClassOptions } from './SomeClass';
import { mock } from '../../__utils';
import { IFoo } from './Foo';
const mockDeps: jest.Mocked<ISomeClassOptions> = {
// foo: {
// myFunc: jest.fn()
// }
foo: mock<IFoo>('myFunc')
};
const someClass = new SomeClass(mockDeps);
describe('SomeClass', () => {
it('#say', () => {
(mockDeps.foo as jest.Mocked<IFoo>).myFunc.mockReturnValueOnce('https://github.com/mrdulin');
const actualValue = someClass.say();
expect(actualValue).toBe('https://github.com/mrdulin');
});
});
__utils.ts:
type GenericFunction = (...args: any[]) => any;
type PickByTypeKeyFilter<T, C> = {
[K in keyof T]: T[K] extends C ? K : never;
};
type KeysByType<T, C> = PickByTypeKeyFilter<T, C>[keyof T];
type ValuesByType<T, C> = {
[K in keyof T]: T[K] extends C ? T[K] : never;
};
type PickByType<T, C> = Pick<ValuesByType<T, C>, KeysByType<T, C>>;
type MethodsOf<T> = KeysByType<Required<T>, GenericFunction>;
type InterfaceOf<T> = PickByType<T, GenericFunction>;
type PartiallyMockedInterfaceOf<T> = {
[K in MethodsOf<T>]?: jest.Mock<InterfaceOf<T>[K]>;
};
export function mock<T>(...mockedMethods: Array<MethodsOf<T>>): jest.Mocked<T> {
const partiallyMocked: PartiallyMockedInterfaceOf<T> = {};
mockedMethods.forEach(mockedMethod => (partiallyMocked[mockedMethod] = jest.fn()));
return partiallyMocked as jest.Mocked<T>;
}
Unit test result:
PASS src/stackoverflow/50217960/index.spec.ts
SomeClass
✓ #say (3ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.207
You can find related issue on GitHub: https://github.com/facebook/jest/issues/7832#issuecomment-527449428
来源:https://stackoverflow.com/questions/50217960/typescript-mock-interface-with-mapped-types