Имитация зависимости в jest с машинописным текстом


94

При тестировании модуля, который имеет зависимость в другом файле. При назначении этого модуля в качестве jest.Mockмашинописного текста возникает ошибка, что метод mockReturnThisOnce(или любой другой метод jest.Mock) не существует в зависимости, это потому, что он был ранее типизирован. Как правильно заставить машинописный текст наследовать типы от jest.Mock?

Вот небольшой пример.

Зависимость

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

Мне кажется, что это очень распространенный вариант использования, и я не знаю, как правильно его вводить. Любая помощь приветствуется!


1
Если я правильно помню, вам нужно издеваться перед импортом. Просто переключите первые 2 строки. Но я в этом не уверен.
— Thomas

3
@ ThomasKleßen Модули, импортированные через ES6 import, оцениваются первыми, независимо от того, добавили ли вы какой-либо код перед импортом. Так что это не сработает.
— mgol 07

@Thomas Вызов jest.mock поднимается в начало кода - я думаю, магия шутки ... ( ref ) Однако это создает некоторые подводные камни, например, при вызове jest.mock () с параметром фабрики модуля, поэтому назовите имитационные функции asmock...
— Тоби

Ответы:


98

Вы можете использовать приведение типов, и оно 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 и TS.

Когда импортированный элемент является классом, вам не обязательно использовать 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может быть либо классом, либо типом, либо интерфейсом


3
Мне пришлось использовать jest.fn(() =>...вместо jest.fn<TestedClassDependency>(() =>...(я просто удалил приведение типов после jest.fn), потому что IntelliJ жалуется. В противном случае этот ответ мне помог, спасибо! Используя это в моем package.json: "@ types / jest": "^ 24.0.3"
— А. Массон

что делает jest.mock('./SomeClass');в приведенном выше коде?
— Reza

11
Хм, он больше не работает с последней версией TS и шуткой 24 :(
— Винсент

1
@Reza, это автоматический макет, jestjs.io/docs/en/es6-class-mocks#automatic-mock
— Брюс Ли

5
<jest.Mock<SomeClass>>SomeClassВыражение вызывает ошибку TS для меня: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

61

Используйте mockedпомощника, ts-jestкак описано здесь

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

и если

  • ты используешь tslint
  • ts-jest находится в ваших dev-зависимостях,

добавьте это правило в свой tslint.json:"no-implicit-dependencies": [true, "dev"]


Вот еще несколько примеров использования ts-jestклассов и: github.com/tbinna/ts-jest-mock-examples и этот пост: stackoverflow.com/questions/58639737/…
— Тоби,

4
Это гораздо лучший ответ, чем ответ, получивший наибольшее количество голосов.
— fakeplasticandroid

@Tobi Тест в репо не проходит
— Kreator

Спасибо за предупреждение @Kreator. Вы видите ту же проблему, о которой сообщалось ? Я пока не смог воспроизвести ни одной проблемы.
— Тоби,

@Kreator только что объединил PR. Сообщите мне, если проблема не исчезнет
— Тоби

18

Я использую шаблон из @ types / jest / index.d.ts чуть выше type def для Mocked (строка 515):

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

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

2
Я почти уверен, что вы могли бы просто сделатьconst myApi = new Api() as jest.Mocked<Api>;
— snowfrogdev

4
@neoflash: не в строгом режиме в TypeScript 3.4 - он будет жаловаться на то, что тип Api недостаточно перекрывается с jest.Mock<Api>. Вам придется согласиться, const myApi = new Api() as any as jest.Mock<Api>и я бы сказал, что приведенное выше выглядит немного лучше, чем двойное утверждение.
— paolostyle

@tuptus: свежий ли строгий режим для 3.4? У вас есть ссылка по этому поводу?
— elmpp

@elmpp: не понимаю, что вы имеете в виду. Под «строгим режимом» я имел "strict": trueв виду наличие в tsconfig.json. Это касается таких вещей, как noImplicitAnyи strictNullChecksт. Д., Поэтому вам не нужно устанавливать для них значение true.
— paolostyle 07

Я не понимаю. Почему вы только гася метод одного экземпляра, то есть myApi? Обычно он не заглушает все другие экземпляры, инициированные классом Apiв тестируемом модуле, верно?
— Иван Ван

14

Есть два решения, оба используют желаемую функцию.

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лайке mockReturnValueили mockResolvedValue https://jestjs.io/docs/en/mock-function-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction может использоваться обычно для ожидания

expect(mockMyFunction).toHaveBeenCalledTimes(1);

7

В ролях as jest.Mock

Простое приведение функции к jest.Mockдолжно помочь:

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


6

Вот что я сделал с 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
>;

4

Я нашел это в @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/DefinitiTyped/DefinitiTyped/blob/master/types/jest/index.d.ts#L1089


0

Недавняя библиотека решает эту проблему с помощью плагина 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полностью безопасны. Кроме того, tsserver знает, что он depencencyбыл импортирован и назначен, depпоэтому все автоматические рефакторинги, поддерживаемые tsserver, тоже будут работать.

Примечание: я поддерживаю библиотеку.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.