Имитация зависимости в 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')

exmaxx
источник
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, тоже будут работать.

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

Mostruash
источник