Знущання над залежністю в жарті з машинописом


94

Під час тестування модуля, який має залежність в іншому файлі. Коли присвоєння цього модуля jest.Mockтипограмою видає помилку, що метод mockReturnThisOnce(або будь-який інший метод jest.Mock) не існує на залежності, це тому, що він раніше вводився. Який правильний спосіб отримати typecript для успадкування типів з 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 рядки. Але я не впевнений у цьому.
— Томас

3
Модулі @ ThomasKleßen, імпортовані за допомогою ES6 import, оцінюються спочатку, незалежно від того, якщо перед імпортом ви вставите якийсь код. Тож це не спрацює.
— mgol

@Thomas Виклики до jest.mock піднімаються до верхньої частини коду - jest magic, я думаю ... ( ref ) Однак це створює деякі підводні камені, наприклад, під час виклику jest.mock () із параметром заводу модуля, тому називають макетні функції якmock...
— Тобі

Відповіді:


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;

Якщо ви не хочете використовувати автоматичний макет і віддаєте перевагу створити його вручну

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 скаржиться. Інакше ця відповідь мені допомогла! Використовуючи це в моєму пакеті.json: "@ types / jest": "^ 24.0.3"
— А. Массон,

що робиться jest.mock('./SomeClass');у наведеному вище коді?
— Реза

11
Хам, це вже не працює з останньою версією TS і жартом 24 :(
— Вінсент,


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

62

Скористайтеся 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 знаходиться у ваших залежностях від розробника,

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


Ось ще кілька прикладів використання ts-jestта класів: github.com/tbinna/ts-jest-mock-examples та цей допис: stackoverflow.com/questions/58639737/…
— Тобі

5
Це набагато краща відповідь, ніж відповідь з найбільшою оцінкою.
— fakeplasticandroid

@Tobi Тест в репо не вдається
— Kreator

Дякуємо за огляд @Kreator. Ви бачите ту саму проблему, що і та, про яку повідомляли ? Я ще не міг відтворити жодного випуску.
— Тобі

@Kreator щойно об’єднав PR. Повідомте мене, якщо проблема не зникне
— Тобі,

18

Я використовую шаблон із @ types / jest / index.d.ts трохи вище типу 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т.д., так що ви не повинні встановити його правильно і для них в окремо.
— паолостиль

Я не розумію. Чому ви заглушаєте метод лише одного екземпляра, тобто myApi? Це загалом не буде заглушати всі інші екземпляри, ініційовані класом Apiу модулі, що тестується, чи не так?
— Іван Ван

14

Існує два рішення, обидва виконують необхідну функцію

1) Використовуйте жарт. MockedFunction

import * as dep from './dependency';

jest.mock('./dependency');

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

2) Використовуйте жарт

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

Між цими двома рішеннями немає різниці. Другий коротший, і тому я б запропонував використовувати його.

Обидва рішення кастингу дозволяють викликати будь-яку функцію жарту на mockMyFunctionlike 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/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089


1

Використання as jest.Mockі нічого іншого

Найкоротший спосіб знущання над модулем, експортованим, як defaultу ts-jest, який я пригадую, справді зводиться до викидання модуля як jest.Mock.

Код:

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

Переваги:

  • не вимагає посилання на defaultвластивість в будь-якому місці тестового коду - замість цього ви посилаєтесь на фактичне імпортоване ім’я функції,
  • ви можете використовувати ту саму техніку для знущань над названим експортом,
  • ні * asв заяві про імпорт,
  • відсутність складного кастингу за typeofключовим словом,
  • ніяких зайвих залежностей типу mocked.

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.