Як я можу знущатися над імпортом модуля ES6 за допомогою Jest?


281

Я починаю вважати, що це неможливо, але все одно хочу запитати.

Я хочу перевірити, що один із моїх модулів ES6 певним чином викликає інший модуль ES6. З Жасмином це дуже просто -

Код програми:

// myModule.js
import dependency from './dependency';

export default (x) => {
  dependency.doSomething(x * 2);
}

І тестовий код:

//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    spyOn(dependency, 'doSomething');

    myModule(2);

    expect(dependency.doSomething).toHaveBeenCalledWith(4);
  });
});

Що еквівалентно Jest? Я відчуваю, що це так просто хочеться зробити, але я рвав волосся, намагаючись зрозуміти це.

Найближче до мене прийшло замінити imports на requires і перемістити їх всередині тестів / функцій. Жодна з яких речей я не хочу робити.

// myModule.js
export default (x) => {
  const dependency = require('./dependency'); // yuck
  dependency.doSomething(x * 2);
}

//myModule-test.js
describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    jest.mock('../dependency');

    myModule(2);

    const dependency = require('../dependency'); // also yuck
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

Щодо бонусних балів, я хотів би, щоб вся справа працювала, коли функція всередині dependency.js- це експорт за замовчуванням. Однак я знаю, що шпигунство на експорт за замовчуванням не працює в Жасмині (або, принаймні, я ніколи не міг би змусити його працювати), тому я не витримую надії, що це можливо і в Jest.


Я так чи інакше використовую Babel для цього проекту, тому я не заперечую продовжувати транспілювати imports в requires. Дякую за голови вгору, хоча.
Cam Jackson

Що робити, якщо у мене є клас A A, і він викликає якусь функцію, то скажемо doSomething () класу B, як ми можемо знущатися, щоб клас A
викликав глузливу

для тих, хто хоче розкрити цю проблему більше github.com/facebook/jest/isissue/936
omeralper

Відповіді:


221

Мені вдалося це вирішити, використовуючи хак із залученням import *. Він навіть працює як для названого, так і за замовчуванням експорту!

Для названого експорту:

// dependency.js
export const doSomething = (y) => console.log(y)

// myModule.js
import { doSomething } from './dependency';

export default (x) => {
  doSomething(x * 2);
}

// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.doSomething = jest.fn(); // Mutate the named export

    myModule(2);

    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

Або для експорту за замовчуванням:

// dependency.js
export default (y) => console.log(y)

// myModule.js
import dependency from './dependency'; // Note lack of curlies

export default (x) => {
  dependency(x * 2);
}

// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.default = jest.fn(); // Mutate the default export

    myModule(2);

    expect(dependency.default).toBeCalledWith(4); // Assert against the default
  });
});

Як цілком справедливо зазначав Михайло Даміан нижче, це мутує модульний об'єкт dependency, і тому він "просочиться" на інші тести. Отже, якщо ви використовуєте такий підхід, вам слід зберігати початкове значення, а потім встановлювати його знову після кожного тесту. Щоб зробити це легко за допомогою Jest, використовуйте метод spyOn () замість того, jest.fn()що він підтримує легке відновлення початкового значення, тому уникаючи раніше згаданих "протікань".


Дякую, що поділились. Я думаю , що чистий результат схожий на це - але це може бути чистіше - stackoverflow.com/a/38414160/1882064
arcseldon

64
Це працює, але це, мабуть, не є хорошою практикою. Зміни в об'єктах, що не входять до складу тесту, схоже, зберігаються між тестами. Це згодом може призвести до несподіваних результатів в інших тестах.
Михай Даміан

10
Замість використання jest.fn (), ви можете використовувати jest.spyOn (), щоб ви могли пізніше відновити початковий метод, тому він не кровоточить в інші тести. Тут я знайшов приємну статтю про різні підходи (jest.fn, jest.mock і jest.spyOn): medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c .
Мартінсос

2
Лише зауваження: якщо dependencyперебуває у тому самому файлі myModule, що і він, він не працюватиме.
Лу Тран

2
Я думаю, що це не буде працювати з Typescript, тому що об'єкт, який ви мутуєте, є лише для читання.
adredx

172

Ви повинні знущатися над модулем і встановлювати шпигуна самостійно:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
  doSomething: jest.fn()
}))

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

4
Це не здається правильним. Я розумію: babel-plugin-jest-hoist: The second argument of jest.mock must be a function.значить, код навіть не компілюється.
Cam Jackson

3
Вибачте, я оновив свій код. Зауважте також, що шлях у jest.mockвідносно тестового файлу.
Andreas Köberle

1
Однак це працювало для мене не тоді, коли використовується експорт за замовчуванням.
Іріс Шаффер

4
@IrisSchaffer, щоб ця робота працювала з експортом за замовчуванням, який потрібно додати __esModule: trueдо об'єкта макету. Це внутрішній прапор, який використовується перекладеним кодом, щоб визначити, чи це перекладений модуль es6 або модуль commonjs.
Йоганнес Лумпе

24
jest.mock('../dependency', () => ({ default: jest.fn() }))
Знущання над

50

Щоб знущатися з експорту за замовчуванням модуля залежності ES6 за допомогою vice:

import myModule from '../myModule';
import dependency from '../dependency';

jest.mock('../dependency');

// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);

describe('myModule', () => {
  it('calls the dependency once with double the input', () => {
    myModule(2);

    expect(dependency).toHaveBeenCalledTimes(1);
    expect(dependency).toHaveBeenCalledWith(4);
  });
});

Інші варіанти не спрацювали для мого випадку.


6
який найкращий спосіб очистити це, якщо я просто хочу зробити один тест? всередині після кожного? `` `` afterEach (() => {jest.unmock (../ залежність ');}) `` `
nxmohamad

1
@falsarella чи дійсно doMock працює в такому випадку? У мене дуже схоже питання, і він нічого не робить, коли я намагаюся джей.doMock всередині конкретного тесту, де jest.mock для всього модуля працює правильно
Progress1ve

1
@ Progress1ve ви можете спробувати скористатися jest.mock з mockImplementationOnce також
falsarella

1
Так, це правдива пропозиція, однак, що вимагає, щоб тест був першим, і я не прихильник писати тести таким чином. Я вирішив ці проблеми, імпортуючи зовнішній модуль та використовуючи spyOn для певних функцій.
Progress1ve

1
@ Progress1ve hmm Я мав на увазі розмістити mockImplementationOnce всередині кожного конкретного тесту ... все одно, я щасливий, що ти знайшов рішення :)
falsarella

38

Додавання ще до відповіді Андреаса. У мене була така ж проблема з кодом ES6, але не хотілося мутувати імпорт. Це виглядало гакітно. Тому я це зробив

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
  });
});

І додано залежність.js у папку "__ mocks __" паралельно залежності. Це працювало для мене. Крім того, це дало мені можливість повернути відповідні дані з макетної реалізації. Переконайтесь, що ви вказали правильний шлях до модуля, який ви хочете знущатися.


Дякую за це Спробую. У обране це рішення теж - stackoverflow.com/a/38414160/1882064
arcseldon

Що мені подобається в цьому підході, це те, що він дає вам можливість надати один ручний макет на всі випадки, коли ви хочете знущатися над певним модулем. У мене, наприклад, є помічник перекладу, який використовується в багатьох місцях. __mocks__/translations.jsФайл просто по замовчуванням експорт jest.fn()в чому - щось на кшталт:export default jest.fn((id) => id)
Iris Шаффер

Ви також можете використовувати jest.genMockFromModuleдля генерації макетів з модулів. facebook.github.io/jest/docs/…
Varunkumar Nagarajan

2
Варто зазначити одне, що модулі ES6, з яких знущаються через export default jest.genMockFromModule('../dependency'), матимуть усі свої функції, призначені dependency.defaultпісля виклику `vice.mock ('.. залежність'), але в іншому випадку поводяться так, як очікувалося.
jhk

7
Як виглядає ваше тестове твердження? Це здається важливою частиною відповіді. expect(???)
камінь

14

Швидкий перехід до 2020 року, я знайшов це посилання для вирішення. використовуючи лише синтаксис модуля ES6 https://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};

// esModule.test.js
jest.mock('./esModule', () => ({
  __esModule: true, // this property makes it work
  default: 'mockedDefaultExport',
  namedExport: jest.fn(),
}));

import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function

Також одне, що вам потрібно знати (що знадобилося мені час, щоб розібратися), це те, що ви не можете викликати jest.mock () всередині тесту; ви повинні викликати його на верхньому рівні модуля. Однак ви можете викликати mockImplementation () всередині окремих тестів, якщо ви хочете встановити різні макети для різних тестів.


5

На питання вже відповіли, але ви можете вирішити його так:

залежність.js

const doSomething = (x) => x
export default doSomething;

myModule.js:

import doSomething from "./dependency";

export default (x) => doSomething(x * 2);

myModule.spec.js:

jest.mock('../dependency');
import doSomething from "../dependency";
import myModule from "../myModule";

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    doSomething.mockImplementation((x) => x * 10)

    myModule(2);

    expect(doSomething).toHaveBeenCalledWith(4);
    console.log(myModule(2)) // 40
  });
});

Але "вимагають" синтаксис CommonJS - ОП запитували про модулі ES6
Енді,

@Анді дякую за ваш коментар, я оновив свою відповідь. BTW те ж саме в логіці.
Тонкий

2

Я вирішив це іншим способом. Скажімо, у вас є залежність.js

export const myFunction = () => { }

Я створюю файл depdency.mock.js окрім нього із таким вмістом:

export const mockFunction = jest.fn();

jest.mock('dependency.js', () => ({ myFunction: mockFunction }));

і в тесті, перш ніж імпортувати файл, який використовує залежність, яку я використовую:

import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'

it('my test', () => {
    mockFunction.returnValue(false);

    functionThatCallsDep();

    expect(mockFunction).toHaveBeenCalled();

})
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.