javascript - 在 Jest 中使用 TypeScript 模擬依賴
當測試一個在不同檔案中有依賴的模組,並將該模組指定為 jest.mock 時,TypeScript 會報錯,提示方法 mockReturnThisOnce(或任何其他 jest.mock 方法)在依賴上不存在,這是因為它先前已被類型化。
如何正確地讓 TypeScript 繼承 jest.mock 的類型?
這裡有一個快速的範例。
依賴
const myDep = (name: string) => name;
export default myDep;
test.ts
import * as dep from '../dependency';
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')
}
我覺得這是非常常見的使用案例,不太確定該怎麼正確地打字。
答案 #1
你可以使用類型轉換,你的 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 來獲取它的類型。
這裡是我在使用 Jest 和 TypeScript 工作中發現的一些其他有用模式
當導入的元素是一個類別時,你不需要使用 typeof,例如:
import { SomeClass } from './SomeClass';
jest.mock('./SomeClass');
const mockedClass = <jest.Mock<SomeClass>>SomeClass;
當你需要模擬一些 Node 原生模組時,這個解決方案也很有用:
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,可以是類別、型別或介面
答案#2
依據文件使用 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)
})
答案#3
有兩種解決方案測試於 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);
答案#4
我在 Mocked 的類型定義(第 515 行)上方使用了來自 @types/jest/index.d.ts 的範例:
import { Api } from "../api";
jest.mock("../api");
const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");
答案#5
將 as jest.Mock 進行類型轉換
只需將函數轉換為 jest.Mock 就可以解決問題:
(dep.default as jest.Mock).mockReturnValueOnce('return')
答案#6
最新的 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');
});
