出于测试目的,我希望能够创建一个实现接口的对象,只有我测试所需的功能,而不必手动维护具有所有可能属性的模拟对象。一般来说,我一次只使用一个函数,所以不需要定义所有其他函数,但我不希望TS继续抱怨缺少属性。
例如,我有一个界面IFoo
:
interface IFoo {
myFunc(): string;
otherFunc(): number;
}
我尝试创建一个映射类型,将jest.Mock<{}>
分配给IFoo
的所有属性
type Mockify<T> = {
[P in keyof T]: jest.Mock<{}>
};
这样称呼:
const mockFoo: Mockify<IFoo> = {
otherFunc: {
// Mocked function behavior
}
}
这种方法的问题是TS抱怨传递给Mockify的对象缺少myFunc
属性。
我还尝试Mockify<Partial<IFoo>>
忽略遗漏的属性,但我的类型定义与IFoo
不同,而IFoo
的其他一些函数正在抱怨。
我也可以将所有属性定义为optionnal,但从概念上讲我不喜欢它。
我觉得映射类型可以完成工作,但我可能没有正确的方法。
感谢您的帮助!
答案 0 :(得分:0)
我可以在映射的类型定义中选择所有属性:
type Mockify<T> = {
[P in keyof T]?: jest.Mock<{}>
};
这相当于每次使用Partial
时都使用Mockify
。
然后,当稍后使用我的模拟对象时,键入断言会将其强制转换回原始界面并且每个人都很高兴
const mockFoo: Mockify<IFoo> = {
otherFunc: {
// Mocked function behavior
}
}
dependentFunc(mockFoo as IFoo);
答案 1 :(得分:0)
如果我理解正确,则您正在尝试部分模拟类型。解决方法如下:
版本:
"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 };
将Foo
中的SomeClass
类用作依赖项:
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 };
单元测试中,我们只能使用foo
帮助函数使用myFunc
方法来部分模拟mock
。它将处理打字稿的类型问题。
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>;
}
单元测试结果:
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
您可以在GitHub上找到相关问题:https://github.com/facebook/jest/issues/7832#issuecomment-527449428