[英]Mock dependency in Jest with TypeScript
當測試在不同文件中具有依賴關系的模塊並將該模塊分配為jest.mock
時,TypeScript 會給出一個錯誤,即方法mockReturnThisOnce
(或任何其他jest.mock
方法)在依賴項中不存在,這是因為它是以前鍵入的。
讓 TypeScript 從jest.mock
繼承類型的正確方法是什么?
這是一個簡單的例子。
依賴
const myDep = (name: string) => name;
export default myDep;
測試.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')
}
我覺得這是一個非常常見的用例,不知道如何正確輸入。
您可以使用類型轉換,您的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 編譯器不知道jest.mock('../dependency');
更改dep
的類型,因此您必須使用類型轉換。 由於導入的dep
不是類型定義,因此您必須使用typeof dep.default
獲取其類型。
當導入的元素是一個類時,您不必使用 typeof 例如:
import { SomeClass } from './SomeClass';
jest.mock('./SomeClass');
const mockedClass = <jest.Mock<SomeClass>>SomeClass;
當您必須模擬一些節點本機模塊時,此解決方案也很有用:
import { existsSync } from 'fs';
jest.mock('fs');
const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;
如果您不想使用 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()
創建模擬對象實例TestedClassDependency
可以是類或類型或接口
使用此處解釋的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)
})
針對TypeScript 版本 3.x 和 4.x測試了兩種解決方案,兩者都在轉換所需的功能
1) 使用 jest.MockedFunction
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;
2) 使用 jest.Mock
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.default as jest.Mock;
這兩種解決方案之間沒有區別。 第二個更短,因此我建議使用那個。
兩種鑄造解決方案都允許在mockMyFunction
上調用任何 jest 模擬函數,例如mockReturnValue
或mockResolvedValue
https://jestjs.io/docs/en/mock-function-api.html
mockMyFunction.mockReturnValue('value');
mockMyFunction
可以正常用於 expect
expect(mockMyFunction).toHaveBeenCalledTimes(1);
我使用來自 @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
只需將函數轉換為jest.Mock
就可以了:
(dep.default as jest.Mock).mockReturnValueOnce('return')
as jest.Mock
,僅此而已我能想到的在 ts-jest 中模擬default
導出的模塊的最簡潔方法實際上歸結為將模塊轉換為jest.Mock
。
代碼:
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()
})
好處:
default
屬性 - 您可以引用實際導出的函數名稱,* as
在 import 語句中,typeof
關鍵字進行復雜的轉換,mocked
這樣的額外依賴項。這是我對jest@24.8.0和ts-jest@24.0.2 所做的:
資源:
class OAuth {
static isLogIn() {
// return true/false;
}
static getOAuthService() {
// ...
}
}
測試:
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
});
});
這是模擬非默認類及其靜態方法的方法:
jest.mock('../src/to/the/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
這應該是從您的類的類型到jest.MockedClass
或類似的類型的某種類型轉換。 但它總是以錯誤告終。 所以我只是直接使用它,它工作。
test('Some test', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
});
但是,如果它是一個函數,您可以模擬它並進行類型對話。
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
>;
我在@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);
*/
注意:當您執行const mockMyFunction = myFunction
然后執行類似mockFunction.mockReturnValue('foo')
時,您也是一個不斷變化的myFunction
。
來源: https ://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089
從 Jest 24.9.0
開始,您可以模擬和正確鍵入 Class/Object/function 和 Jest 屬性。
對於類型化的模擬,我們想要的是模擬對象類型包含模擬對象類型和 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');
如您所見,您可以手動轉換您需要的內容,或者您需要一些東西來遍歷所有foo的屬性/方法來鍵入/轉換所有內容。
為此(深度模擬類型),您可以使用 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
Artur Górski 評價最高的解決方案不適用於最后一個 TS 和 Jest。 使用模擬類
import SoundPlayer from '../sound-player';
jest.mock('../sound-player'); // SoundPlayer is now a mock constructor
const SoundPlayerMock = SoundPlayer as jest.MockedClass<typeof SoundPlayer>;
最新的 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
這很丑陋,實際上擺脫這種丑陋是我什至看這個問題的原因,但是要從模塊模擬中獲得強類型,您可以執行以下操作:
const myDep = (require('./dependency') as import('./__mocks__/dependency')).default;
jest.mock('./dependency');
確保您需要'./dependency'
而不是直接模擬,否則您將獲得兩個不同的實例化。
對我來說,這就足夠了:
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')
})
},
}
})
然后,無論何時調用 add 方法,它都會執行上面的代碼,而不是將其推送到隊列中,在這種情況下。
使用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")
})
由於我們在談論測試,因此一種快速而骯臟的方法是僅告訴TypeScript忽略此行:
//@ts-ignore
dep.default.mockReturnValueOnce('return')
最近的一個庫用 babel 插件解決了這個問題: https ://github.com/userlike/joke
例子:
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');
}
請注意dep
和mockReturnValueOnce
是完全類型安全的。 最重要的是, depencency
知道依賴已導入並分配給dep
,因此 tsserver 支持的所有自動重構也將起作用。
注意:我維護圖書館。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.