網站搜尋

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');
});