У меня есть следующий модуль, который я пытаюсь протестировать в Jest:

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}

Как показано выше, он экспортирует некоторые именованные функции и, что важно, testFn использует otherFn.

В Jest, когда я пишу свой модульный тест для testFn, я хочу имитировать функцию otherFn, потому что мне не нужны ошибки в otherFnчтобы повлиять на мой модульный тест для testFn. Моя проблема в том, что я не уверен, как это лучше сделать:

// myModule.test.js
jest.unmock('myModule');

import { testFn, otherFn } from 'myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    // I want to mock "otherFn" here but can't reassign
    // a.k.a. can't do otherFn = jest.fn()
  });
});

Любая помощь / понимание приветствуется.

Ответы (8)

Используйте jest.requireActual () внутри jest.mock ()

jest.requireActual (имя_модуля)

Возвращает фактический модуль вместо макета, минуя все проверки того, должен ли модуль получить макет реализации или нет.

Пример

Я предпочитаю это краткое использование, когда вы требуете и распространяете внутри возвращаемого объекта:

// myModule.test.js

импортировать {otherFn} из './myModule.js'

jest.mock ('./ myModule.js', () => ({
  ... (jest.requireActual ('./ myModule.js')),
  otherFn: jest.fn ()
}))

описать ('категория тестирования', () => {
  it ('проверяет что-то по поводу otherFn', () => {
    otherFn.mockReturnValue ('фу')
    ожидать (otherFn ()). toBe ('фу')
  })
})

Этот метод также упоминается в документации Jest's Manual Mocks (ближе к концу Примеры):

Чтобы гарантировать синхронизацию ручного макета и его реальной реализации, может быть полезно потребовать реальный модуль, используя jest.requireActual (moduleName) в вашем ручном макете и изменив его с помощью макетных функций перед экспорт.

Похоже, я опоздал на вечеринку, но да, это возможно.

testFn просто нужно вызвать otherFn с помощью модуля.

Если testFn использует модуль для вызова otherFn, тогда экспорт модуля для otherFn можно смоделировать, а testFn вызовет макет .


Вот рабочий пример:

myModule.js

импортировать * как myModule из './myModule'; // импортируем myModule в себя

функция экспорта otherFn () {
  вернуть "исходное значение";
}

функция экспорта testFn () {
  константный результат = myModule.otherFn (); // вызываем otherFn с помощью модуля

  // делаем другие вещи

  вернуть результат;
}

myModule.test.js

импортировать * как myModule из './myModule';

описать ('категория тестирования', () => {
  it ('проверяет что-то о testFn', () => {
    const mock = jest.spyOn (myModule, 'otherFn'); // шпионить за otherFn
    mock.mockReturnValue ('фиктивное значение'); // имитируем возвращаемое значение

    ожидать (myModule.testFn ()). toBe ('фиктивное значение'); // УСПЕХ

    mock.mockRestore (); // восстанавливаем otherFn
  });
});

На основе ответа Брайана Адамса именно так я смог использовать тот же подход в TypeScript. Более того, используя jest.doMock (), можно смоделировать функции модуля только в некоторых конкретных тестах тестового файла и предоставить индивидуальные макетные реализации для каждого из них.

src / module.ts

импорт * как модуль из './module';

function foo (): string {
  return `foo $ {module.bar ()}`;
}

функциональная панель (): строка {
  вернуть 'бар';
}

экспорт {foo, bar};

test / module.test.ts

импорт {mockModuleParhibited} из './helpers';

импортировать * как модуль из '../src/module';

const {foo} = модуль;

описать ('набор тестов', () => {
  beforeEach (function () {
    jest.resetModules ();
  });

  it ('не имитируйте панель 1', async () => {
    ожидать (foo ()). toEqual ('foobar');
  });

  it ('mock bar', async () => {
    mockModuleParfully ('../ src / module', () => ({
      bar: jest.fn (). mockImplementation (() => 'БАР')
    }));
    const module = await import ('../ src / module');
    const {foo} = модуль;
    ожидать (foo ()). toEqual ('fooBAR');
  });

  it ('не имитируйте панель 2', async () => {
    ожидать (foo ()). toEqual ('foobar');
  });
});

test / helpers.ts

функция экспорта mockModuleParhibited (
  modulePath: строка,
  mocksCreator: (originalModule: any) => Запись <строка, любая>
): пустота {
  const testRelativePath = path.relative (path.dirname (expect.getState (). testPath), __dirname);
  const fixedModulePath = path.relative (testRelativePath, modulePath);
  jest.doMock (fixedModulePath, () => {
    const originalModule = jest.requireActual (fixedModulePath);
    return {... originalModule, ... mocksCreator (originalModule)};
  });
}

Мокирующие функции модуля перемещены в вспомогательную функцию mockModuleParhibited, расположенную в отдельном файле, поэтому его можно использовать из разных тестовых файлов (которые, как правило, могут находиться в других каталогах). Он полагается на expect.getState (). TestPath для исправления пути к макету модуля (modulePath) (сделайте его относительно helpers.ts, содержащего * 100010). * mockModuleParately).mocksCreator функция, переданная в качестве второго аргумента в mockModulePartial, должна возвращать макеты модуля. Эта функция получает originalModule, и имитирующие реализации могут при желании полагаться на него.

import m from '../myModule';

У меня не работает, использовал:

import * as m from '../myModule';

m.otherFn = jest.fn();

В верхней части первого ответа здесь вы можете использовать babel-plugin-rewire для имитации импортированной именованной функции. Вы можете проверить этот раздел поверхностно для переналадка именованной функции.

Одним из непосредственных преимуществ для вашей ситуации здесь является то, что вам не нужно менять способ вызова другой функции из вашей функции.

Я решил свою проблему, сочетая ответы, которые я нашел здесь:

myModule.js

import * as myModule from './myModule';  // import myModule into itself

export function otherFn() {
  return 'original value';
}

export function testFn() {
  const result = myModule.otherFn();  // call otherFn using the module

  // do other things

  return result;
}

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  let otherFnOrig;

  beforeAll(() => {
    otherFnOrig = myModule.otherFn;
    myModule.otherFn = jest.fn();
  });

  afterAll(() => {
    myModule.otherFn = otherFnOrig;
  });

  it('tests something about testFn', () => {
    // using mock to make the tests
  });
});

Транспилированный код не позволит babel получить привязку, на которую ссылается otherFn (). Если вы используете выражение функции, вы сможете добиться имитации otherFn ().

// myModule.js
exports.otherFn = () => {
  console.log('do something');
}

exports.testFn = () => {
  exports.otherFn();

  // do other things
}

// myModule.test.js
import m from '../myModule';

m.otherFn = jest.fn();

Но, как @kentcdodds упоминал в предыдущем комментарии, вы, вероятно, не захотите высмеивать otherFn (). Скорее просто напишите новую спецификацию для otherFn () и смоделируйте все необходимые вызовы, которые он делает.

Так, например, если otherFn () делает http-запрос ...

// myModule.js
exports.otherFn = () => {
  http.get('http://some-api.com', (res) => {
    // handle stuff
  });
};

Здесь вы хотели бы высмеять http.get и обновить свои утверждения на основе ваших имитированных реализаций.

// myModule.test.js
jest.mock('http', () => ({
  get: jest.fn(() => {
    console.log('test');
  }),
}));

Я знаю, что об этом спрашивали давно, но я просто столкнулся с этой самой ситуацией и, наконец, нашел решение, которое будет работать. Так что я подумал, что поделюсь здесь.

Для модуля:

// myModule.js

функция экспорта otherFn () {
  console.log («сделай что-нибудь»);
}

функция экспорта testFn () {
  otherFn ();

  // делаем другие вещи
}

Вы можете изменить на следующее:

// myModule.js

экспорт const otherFn = () => {
  console.log («сделай что-нибудь»);
}

экспорт const testFn = () => {
  otherFn ();

  // делаем другие вещи
}

экспортирует их как константы вместо функций. Я считаю, что проблема связана с подъемом в JavaScript, и использование const предотвращает такое поведение.

Тогда в вашем тесте может быть что-то вроде следующего:

импортировать * как myModule из myModule;


описать ('...', () => {
  jest.spyOn (myModule, 'otherFn'). mockReturnValue ('что бы вы ни хотели вернуть');

  // или

  myModule.otherFn = jest.fn (() => {
    // ваша фиктивная реализация
  });
});

Ваши mocks теперь должны работать так, как вы обычно ожидаете.

2022 WebDevInsider