[英]Mock dependency in Jest with TypeScript
When testing a module that has a dependency in a different file and assigning that module to be a jest.mock
, TypeScript gives an error that the method mockReturnThisOnce
(or any other jest.mock
method) does not exist on the dependency, this is because it is previously typed.当测试在不同文件中具有依赖关系的模块并将该模块分配为
jest.mock
时,TypeScript 会给出一个错误,即方法mockReturnThisOnce
(或任何其他jest.mock
方法)在依赖项中不存在,这是因为它是以前键入的。
What is the proper way to get TypeScript to inherit the types from jest.mock
?让 TypeScript 从
jest.mock
继承类型的正确方法是什么?
Here is a quick example.这是一个简单的例子。
Dependency依赖
const myDep = (name: string) => name;
export default myDep;
test.ts测试.ts
import * as dep from '../depenendency';
jest.mock('../dependency');
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
dep.default.mockReturnValueOnce('return')
}
I feel like this is a very common use case and not sure how to properly type this.我觉得这是一个非常常见的用例,不知道如何正确输入。
You can use type casting and your test.ts
should look like this:您可以使用类型转换,您的
test.ts
应该如下所示:
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = <jest.Mock<typeof dep.default>>dep.default;
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
mockedDependency.mockReturnValueOnce('return');
});
TS transpiler is not aware that jest.mock('../dependency');
TS 编译器不知道
jest.mock('../dependency');
changes type of dep
thus you have to use type casting.更改
dep
的类型,因此您必须使用类型转换。 As imported dep
is not a type definition you have to get its type with typeof dep.default
.由于导入的
dep
不是类型定义,因此您必须使用typeof dep.default
获取其类型。
When imported element is a class then you don't have to use typeof for example:当导入的元素是一个类时,您不必使用 typeof 例如:
import { SomeClass } from './SomeClass';
jest.mock('./SomeClass');
const mockedClass = <jest.Mock<SomeClass>>SomeClass;
This solution is also useful when you have to mock some node native modules:当您必须模拟一些节点本机模块时,此解决方案也很有用:
import { existsSync } from 'fs';
jest.mock('fs');
const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;
In case you don't want to use jest automatic mock and prefer create manual one如果您不想使用 jest 自动模拟并更喜欢创建手动模拟
import TestedClass from './TestedClass';
import TestedClassDependency from './TestedClassDependency';
const testedClassDependencyMock = jest.fn<TestedClassDependency>(() => ({
// implementation
}));
it('Should throw an error when calling playSomethingCool', () => {
const testedClass = new TestedClass(testedClassDependencyMock());
});
testedClassDependencyMock()
creates mocked object instance TestedClassDependency
can be either class or type or interface testedClassDependencyMock()
创建模拟对象实例TestedClassDependency
可以是类或类型或接口
Use the mocked
helper like explained here使用此处解释的
mocked
助手
// foo.spec.ts
import { foo } from './foo'
jest.mock('./foo')
// here the whole foo var is mocked deeply
const mockedFoo = jest.mocked(foo, true)
test('deep', () => {
// there will be no TS error here, and you'll have completion in modern IDEs
mockedFoo.a.b.c.hello('me')
// same here
expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1)
})
test('direct', () => {
foo.name()
// here only foo.name is mocked (or its methods if it's an object)
expect(jest.mocked(foo.name).mock.calls).toHaveLength(1)
})
There are two solutions tested for TypeScript version 3.x and 4.x , both are casting desired function针对TypeScript 版本 3.x 和 4.x测试了两种解决方案,两者都在转换所需的功能
1) Use jest.MockedFunction 1) 使用 jest.MockedFunction
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;
2) Use jest.Mock 2) 使用 jest.Mock
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.default as jest.Mock;
There is no difference between these two solutions.这两种解决方案之间没有区别。 The second one is shorter and I would therefore suggest using that one.
第二个更短,因此我建议使用那个。
Both casting solutions allows to call any jest mock function on mockMyFunction
like mockReturnValue
or mockResolvedValue
https://jestjs.io/docs/en/mock-function-api.html两种铸造解决方案都允许在
mockMyFunction
上调用任何 jest 模拟函数,例如mockReturnValue
或mockResolvedValue
https://jestjs.io/docs/en/mock-function-api.html
mockMyFunction.mockReturnValue('value');
mockMyFunction
can be used normally for expect mockMyFunction
可以正常用于 expect
expect(mockMyFunction).toHaveBeenCalledTimes(1);
I use the pattern from @types/jest/index.d.ts just above the type def for Mocked (line 515):我使用来自 @types/jest/index.d.ts 的模式,就在 Mocked 的类型 def 上方(第 515 行):
import { Api } from "../api";
jest.mock("../api");
const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");
as jest.Mock
as jest.Mock
Simply casting the function to jest.Mock
should do the trick:只需将函数转换为
jest.Mock
就可以了:
(dep.default as jest.Mock).mockReturnValueOnce('return')
as jest.Mock
and nothing else as jest.Mock
,仅此而已The most concise way of mocking a module exported as default
in ts-jest that I can think of really boils down to casting the module as jest.Mock
.我能想到的在 ts-jest 中模拟
default
导出的模块的最简洁方法实际上归结为将模块转换为jest.Mock
。
Code:代码:
import myDep from '../dependency' // No `* as` here
jest.mock('../dependency')
it('does what I need', () => {
// Only diff with pure JavaScript is the presence of `as jest.Mock`
(myDep as jest.Mock).mockReturnValueOnce('return')
// Call function that calls the mocked module here
// Notice there's no reference to `.default` below
expect(myDep).toHaveBeenCalled()
})
Benefits:好处:
default
property anywhere in the test code - you reference the actual exported function name instead,default
属性 - 您可以引用实际导出的函数名称,* as
in the import statement,* as
在 import 语句中,typeof
keyword,typeof
关键字进行复杂的转换,mocked
.mocked
这样的额外依赖项。Here's what I did with jest@24.8.0 and ts-jest@24.0.2 :这是我对jest@24.8.0和ts-jest@24.0.2 所做的:
source:资源:
class OAuth {
static isLogIn() {
// return true/false;
}
static getOAuthService() {
// ...
}
}
test:测试:
import { OAuth } from '../src/to/the/OAuth'
jest.mock('../src/utils/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
describe('createMeeting', () => {
test('should call conferenceLoginBuild when not login', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
// Other tests
});
});
This is how to mock a non-default class and it's static methods:这是模拟非默认类及其静态方法的方法:
jest.mock('../src/to/the/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
Here should be some type conversion from the type of your class to jest.MockedClass
or something like that.这应该是从您的类的类型到
jest.MockedClass
或类似的类型的某种类型转换。 But it always ends up with errors.但它总是以错误告终。 So I just used it directly, and it worked.
所以我只是直接使用它,它工作。
test('Some test', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
});
But, if it's a function, you can mock it and do the type conversation.但是,如果它是一个函数,您可以模拟它并进行类型对话。
jest.mock('../src/to/the/Conference', () => ({
conferenceSuccessDataBuild: jest.fn(),
conferenceLoginBuild: jest.fn()
}));
const mockedConferenceLoginBuild = conferenceLoginBuild as
jest.MockedFunction<
typeof conferenceLoginBuild
>;
const mockedConferenceSuccessDataBuild = conferenceSuccessDataBuild as
jest.MockedFunction<
typeof conferenceSuccessDataBuild
>;
I have found this in @types/jest
:我在
@types/jest
找到了这个:
/**
* Wrap a function with mock definitions
*
* @example
*
* import { myFunction } from "./library";
* jest.mock("./library");
*
* const mockMyFunction = myFunction as jest.MockedFunction<typeof myFunction>;
* expect(mockMyFunction.mock.calls[0][0]).toBe(42);
*/
Note: When you do const mockMyFunction = myFunction
and then something like mockFunction.mockReturnValue('foo')
, you're a changing myFunction
as well.注意:当您执行
const mockMyFunction = myFunction
然后执行类似mockFunction.mockReturnValue('foo')
时,您也是一个不断变化的myFunction
。
Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089来源: https ://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089
As of Jest 24.9.0
here is how you can mock and correctly type both your Class/Object/function and Jest properties.从 Jest
24.9.0
开始,您可以模拟和正确键入 Class/Object/function 和 Jest 属性。
jest.MockedFunction jest.MockedFunction
jest.MockedClass jest.MockedClass
What we would like for a typed mock is that the mocked object type contains the union of the mocked object type and the type of Jest mocks.对于类型化的模拟,我们想要的是模拟对象类型包含模拟对象类型和 Jest 模拟类型的联合。
import foo from 'foo';
jest.mock('foo');
const mockedFoo = foo as jest.MockedFunction<typeof foo>;
// or: const mockedFooClass = foo as jest.MockedClass<typeof FooClass>;
mockedFoo.mockResolvedValue('mockResult');
// Or:
(mockedFoo.getSomething as jest.MockedFunction<typeof mockedFoo.getSomething>).mockResolvedValue('mockResult');
As you can see, you can either manually cast what you need or you'll need something to traverse all foo 's properties/methods to type/cast everything.如您所见,您可以手动转换您需要的内容,或者您需要一些东西来遍历所有foo的属性/方法来键入/转换所有内容。
To do that (deep mock types) you can use jest.mocked() introduced in Jest 27.4.0
为此(深度模拟类型),您可以使用 Jest
27.4.0
中引入的jest.mocked()
import foo from 'foo';
jest.mock('foo');
const mockedFoo = jest.mocked(foo, true);
mockedFoo.mockImplementation() // correctly typed
mockedFoo.getSomething.mockImplementation() // also correctly typed
The top rated solution by Artur Górski does not work with the last TS and Jest. Artur Górski 评价最高的解决方案不适用于最后一个 TS 和 Jest。 Use MockedClass
使用模拟类
import SoundPlayer from '../sound-player';
jest.mock('../sound-player'); // SoundPlayer is now a mock constructor
const SoundPlayerMock = SoundPlayer as jest.MockedClass<typeof SoundPlayer>;
The latest jest allows you to do this very easily with jest.mocked最新的 jest 允许您使用jest.mocked轻松完成此操作
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = jest.mocked(dep);
it('should do what I need', () => {
mockedDependency.mockReturnValueOnce('return');
});
import api from 'api'
jest.mock('api')
const mockApi = (api as unknown) as jest.Mock
This is ugly, and in fact getting away from this ugliness is why I even looked at this question, but to get strong typing from a module mock, you can do something like this:这很丑陋,实际上摆脱这种丑陋是我什至看这个问题的原因,但是要从模块模拟中获得强类型,您可以执行以下操作:
const myDep = (require('./dependency') as import('./__mocks__/dependency')).default;
jest.mock('./dependency');
Make sure you require './dependency'
rather than the mock directly, or you will get two different instantiations.确保您需要
'./dependency'
而不是直接模拟,否则您将获得两个不同的实例化。
For me this was enough:对我来说,这就足够了:
let itemQ: queueItemType
jest.mock('../dependency/queue', () => {
return {
add: async (item: queueItemType, ..._args: any) => {
// then we can use the item that would be pushed to the queue in our tests
itemQ = item
return new Promise(resolve => {
resolve('Mocked')
})
},
}
})
Then, whenever the add method is called it will execute this code above instead of pushing it to the queue, in this case.然后,无论何时调用 add 方法,它都会执行上面的代码,而不是将其推送到队列中,在这种情况下。
With TypeScript 2.8 we can do like this with ReturnType
:使用
ReturnType
2.8 我们可以使用 ReturnType 这样做:
import * as dep from "./depenendency"
jest.mock("./dependency")
const mockedDependency = <jest.Mock<ReturnType<typeof dep.default>>>dep.default
it("should do what I need", () => {
mockedDependency.mockReturnValueOnce("return")
})
Since we're talking about a test, a quick and dirty way is to just to tell TypeScript to ignore this line: 由于我们在谈论测试,因此一种快速而肮脏的方法是仅告诉TypeScript忽略此行:
//@ts-ignore
dep.default.mockReturnValueOnce('return')
A recent library solves this problem with a babel plugin: https://github.com/userlike/joke最近的一个库用 babel 插件解决了这个问题: https ://github.com/userlike/joke
Example:例子:
import { mock, mockSome } from 'userlike/joke';
const dep = mock(import('./dependency'));
// You can partially mock a module too, completely typesafe!
// thisIsAMock has mock related methods
// thisIsReal does not have mock related methods
const { thisIsAMock, thisIsReal } = mockSome(import('./dependency2'), () => ({
thisIsAMock: jest.fn()
}));
it('should do what I need', () => {
dep.mockReturnValueOnce('return');
}
Be aware that dep
and mockReturnValueOnce
are fully type safe.请注意
dep
和mockReturnValueOnce
是完全类型安全的。 On top, tsserver is aware that depencency
was imported and was assigned to dep
so all automatic refactorings that tsserver supports will work too.最重要的是,
depencency
知道依赖已导入并分配给dep
,因此 tsserver 支持的所有自动重构也将起作用。
Note: I maintain the library.注意:我维护图书馆。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.