Mock phụ thuộc trong jest với typecript


94

Khi kiểm tra một mô-đun có phần phụ thuộc vào một tệp khác. Khi gán mô-đun đó là jest.Mocktypecript sẽ đưa ra lỗi rằng phương thức mockReturnThisOnce(hoặc bất kỳ phương thức jest.Mock nào khác) không tồn tại trên phụ thuộc, điều này là do nó đã được gõ trước đó. Cách thích hợp để lấy typecript kế thừa các kiểu từ jest.Mock là gì?

Đây là một ví dụ nhanh.

Sự phụ thuộc

const myDep = (name: string) => name;
export default myDep;

test.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')
}

Tôi cảm thấy như đây là một trường hợp sử dụng rất phổ biến và không biết làm thế nào để nhập đúng cách. Bất kì sự trợ giúp nào đều được đánh giá cao!


2
Nếu tôi nhớ đúng, bạn phải giả lập trước khi nhập. Chỉ cần chuyển 2 dòng đầu tiên. Nhưng tôi không chắc về điều này.
Thomas

3
@ ThomasKleßen Mô-đun được nhập qua ES6 importđược đánh giá đầu tiên, bất kể bạn có đặt một số mã trước khi nhập hay không. Vì vậy, điều này sẽ không hoạt động.
mgol

@Thomas Các lệnh gọi đến jest.mock được đưa lên đầu mã - tôi đoán là ma thuật jest ... (tham khảo ) Tuy nhiên, điều này tạo ra một số cạm bẫy, ví dụ khi gọi jest.mock () với tham số nhà máy mô-đun, do đó đặt tên các hàm giả asmock...
Tobi

Câu trả lời:


98

Bạn có thể sử dụng kiểu truyền và của bạn test.tssẽ trông như thế này:

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

Bộ chuyển tiếp TS không biết rằng jest.mock('../dependency');thay đổi kiểu depdo đó bạn phải sử dụng kiểu đúc. Vì nhập depkhông phải là định nghĩa kiểu, bạn phải lấy kiểu của nó typeof dep.default.

Dưới đây là một số mẫu hữu ích khác mà tôi đã tìm thấy trong quá trình làm việc với Jest và TS

Khi phần tử được nhập là một lớp thì bạn không phải sử dụng typeof, ví dụ:

import { SomeClass } from './SomeClass';

jest.mock('./SomeClass');

const mockedClass = <jest.Mock<SomeClass>>SomeClass;

Giải pháp này cũng hữu ích khi bạn phải mô phỏng một số mô-đun gốc của nút:

import { existsSync } from 'fs';

jest.mock('fs');

const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;

Trong trường hợp bạn không muốn sử dụng mô hình tự động jest và thích tạo mô hình thủ công

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()tạo phiên bản đối tượng giả TestedClassDependencycó thể là lớp hoặc kiểu hoặc giao diện


3
Tôi đã phải sử dụng jest.fn(() =>...thay vì jest.fn<TestedClassDependency>(() =>...(tôi vừa loại bỏ kiểu truyền sau jest.fn) vì IntelliJ đang phàn nàn. Nếu không câu trả lời này đã giúp tôi cảm ơn! Sử dụng cái này trong package.json của tôi: "@ type / jest": "^ 24.0.3"
A. Masson

những gì hiện jest.mock('./SomeClass');trong mã trên?
Reza,

11
Hum nó không hoạt động nữa với phiên bản TS cuối cùng và jest 24 :(
Vincent

1
@Reza nó là mô hình tự động, jestjs.io/docs/en/es6-class-mocks#automatic-mock
Bruce Lee

6
Các <jest.Mock<SomeClass>>SomeClassbiểu hiện đang sản xuất một lỗi TS cho tôi:Conversion of type 'typeof SomeClass' to type 'Mock<SomeClass, any>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'typeof SomeClass' is missing the following properties from type 'Mock<SomeClass, any>': getMockName, mock, mockClear, mockReset, and 11 more.ts(2352)
the21st

62

Sử dụng trình mockedtrợ giúp ts-jestnhư được giải thích ở đây

// foo.spec.ts
import { mocked } from 'ts-jest/utils'
import { foo } from './foo'
jest.mock('./foo')

// here the whole foo var is mocked deeply
const mockedFoo = 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(mocked(foo.name).mock.calls).toHaveLength(1)
})

và nếu

  • bạn dùng tslint
  • ts-jest nằm trong phụ thuộc nhà phát triển của bạn,

thêm quy tắc này vào tslint.json:"no-implicit-dependencies": [true, "dev"]


Dưới đây là một số ví dụ khác về cách sử dụng ts-jestvà các lớp: github.com/tbinna/ts-jest-mock-examples và bài đăng này: stackoverflow.com/questions/58639737/…
Tobi

5
Đây là một câu trả lời tốt hơn nhiều so với câu trả lời được bình chọn cao nhất.
fakeplasticandroid

@Tobi Kiểm tra trong repo không thành công
Kreator

Cảm ơn vì sự quan tâm của @Kreator. Bạn có thấy vấn đề giống như vấn đề được báo cáo không? Tôi không thể tái tạo bất kỳ vấn đề nào.
Tobi

@Kreator vừa hợp nhất một PR. Hãy cho tôi biết nếu sự cố vẫn tiếp diễn
Tobi

18

Tôi sử dụng mẫu từ @ type / jest / index.d.ts ngay trên loại def cho Mocked (dòng 515):

import { Api } from "../api";
jest.mock("../api");

const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");

2
Tôi khá chắc chắn rằng bạn chỉ có thể làm đượcconst myApi = new Api() as jest.Mocked<Api>;
snowfrogdev

4
@neoflash: Không ở chế độ nghiêm ngặt trong TypeScript 3.4 - nó sẽ phàn nàn rằng kiểu Api không đủ trùng lặp với jest.Mock<Api>. Bạn sẽ phải đi với const myApi = new Api() as any as jest.Mock<Api>và tôi muốn nói rằng cái ở trên trông đẹp hơn một chút so với khẳng định kép.
paolostyle

@tuptus: chế độ nghiêm ngặt có mới cho 3,4 không? Bạn có một liên kết xin vui lòng liên quan đến điều này?
elmpp

@elmpp: không rõ ý bạn. Theo "chế độ nghiêm ngặt", tôi có nghĩa là có "strict": truetrong tsconfig.json. Điều này bao gồm những thứ như noImplicitAny, strictNullChecksv.v., vì vậy bạn không cần phải đặt nó thành true cho chúng riêng lẻ.
paolostyle

Tôi không hiểu. Tại sao bạn chỉ khai thác phương thức của một trường hợp, tức là myApi? Nói chung, nó sẽ không khai báo tất cả các trường hợp khác được khởi tạo bởi lớp Apitrong mô-đun đang được kiểm tra, phải không?
Ivan Wang,

14

Có hai giải pháp, cả hai đều là truyền chức năng mong muốn

1) Sử dụng jest.MockedFunction

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;

2) Sử dụng jest.Mock

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

Không có sự khác biệt giữa hai giải pháp này. Cái thứ hai ngắn hơn và do đó tôi sẽ đề nghị sử dụng cái đó.

Cả hai giải pháp truyền đều cho phép gọi bất kỳ hàm giả lập nào trên mockMyFunctionlike mockReturnValuehoặc mockResolvedValue https://jestjs.io/docs/en/mock-osystem-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction có thể được sử dụng bình thường cho mong đợi

expect(mockMyFunction).toHaveBeenCalledTimes(1);

7

Diễn viên as jest.Mock

Chỉ cần truyền hàm để jest.Mockthực hiện thủ thuật:

(dep.default as jest.Mock).mockReturnValueOnce('return')


6

Đây là những gì tôi đã làm với jest@24.8.0ts-jest@24.0.2 :

nguồn:

class OAuth {

  static isLogIn() {
    // return true/false;
  }

  static getOAuthService() {
    // ...
  }
}

kiểm tra:

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

Đây là cách bắt chước một lớp không phải mặc định và đó là các phương thức tĩnh:

jest.mock('../src/to/the/OAuth', () => ({
  OAuth: class {
    public static getOAuthService() {
      return {
        getAuthorizationUrl() {
          return '';
        }
      };
    }
  }
}));

Đây sẽ là một số chuyển đổi kiểu từ kiểu lớp của bạn thành jest.MockedClasshoặc một cái gì đó tương tự. Nhưng nó luôn luôn có lỗi. Vì vậy, tôi chỉ sử dụng nó trực tiếp, và nó đã hoạt động.

test('Some test', () => {
  OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
    return false;
  });
});

Nhưng, nếu đó là một hàm, bạn có thể chế nhạo nó và thực hiện cuộc trò chuyện kiểu.

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
>;

4

Tôi đã tìm thấy điều này trong @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);
*/

Lưu ý: Khi bạn làm const mockMyFunction = myFunctionvà sau đó một cái gì đó giống như mockFunction.mockReturnValue('foo'), bạn cũng đang thay đổi myFunction.

Nguồn: https://github.com/DefinifinityTyped/DefinifinityTyped/blob/master/types/jest/index.d.ts#L1089


1

Sử dụng as jest.Mockvà không có gì khác

Cách ngắn gọn nhất để chế nhạo một mô-đun được xuất như defaulttrong ts-jest mà tôi có thể nghĩ ra thực sự là đúc mô-đun jest.Mock.

Mã:

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()
})

Những lợi ích:

  • không yêu cầu tham chiếu đến thuộc defaulttính ở bất kỳ đâu trong mã thử nghiệm - thay vào đó, bạn tham chiếu đến tên hàm được xuất thực tế,
  • bạn có thể sử dụng kỹ thuật tương tự để chế nhạo các bản xuất có tên,
  • không có * astrong câu lệnh nhập,
  • không sử dụng typeoftừ khóa phức tạp ,
  • không có phụ thuộc như mocked.

0

Thư viện gần đây giải quyết vấn đề này bằng plugin babel: https://github.com/userlike/joke

Thí dụ:

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

Hãy biết rằng depmockReturnValueOncehoàn toàn là loại an toàn. Trên hết, tsserver biết rằng nó depencencyđã được nhập và được gán cho depnên tất cả các cấu trúc lại tự động mà tsserver hỗ trợ cũng sẽ hoạt động.

Lưu ý: Tôi duy trì thư viện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.