Заменить конкретный модуль при тестировании

Я тестирую приложение React-Redux с помощью Jest и как часть этого в моих вызовах API, я импортирую модуль fetch cross-fetch. Я хочу переопределить или заменить это на fetch-mock. Вот моя файловая структура:

Action.js

import fetch from 'cross-fetch';
export const apiCall = () => {
    return fetch('http://url');

Action.test.js

import fetchMock from 'fetch-mock';
import { apiCall } from './Action';
fetchMock.get('*', { hello: 'world' });
describe('actions', () => {
    apiCall().then(
        response => {
            console.log(response)
        })
})

Очевидно, что в этот момент я не настроил тест. Поскольку перекрестная выборка импортируется ближе к функции, она использует ее реализацию fetch, заставляя ее выполнять фактический вызов вместо моего макета. Каков наилучший способ получить выборку для издевательства (кроме удаления строки import fetch from 'cross-fetch')?

Есть ли способ сделать условный импорт в зависимости от того, называется ли node script test или build? Или задайте приоритетный выбор издевательства?

Ответ 1

Если ваш проект является проектом webpack, то https://github.com/plasticine/inject-loader очень полезен. Вы можете просто поменять любую зависимость с макетом всего несколькими строками кода.

describe('MyModule', () => {
  let myModule;
  let dependencySpy;

  beforeEach(() => {
    dependencySpy= // {a mock/spy};
    myModule = require('inject-loader!./MyModule')({
      'cross-fetch': {dependencySpy},
    });
  });

  it('should call fetch', () => {
    myModule.function1();
    expect(dependencySpy.calls.length).toBe(1);
  });

});

Примечание. Убедитесь, что вы не импортируете тестируемый модуль в верхней части файла. вызов require выполняет эту часть.

Ответ 2

fetch-mock не предназначен для замены вызовов fetch() в тестируемом коде, и вам не нужно изменять или удалять импорт. Вместо этого он предоставляет макетные ответы во время ваших тестов, чтобы запросы, сделанные с помощью fetch(), получали известные надежные ответы.

Ответ 3

Есть несколько способов решения этой проблемы.

  • Вы можете заглушить модули без инъекции зависимости, используя Sinon.

  • Используйте lib, называемый rewire, чтобы высмеять импортированные методы в процедурной звонки

  • Запишите оригинальную функцию, чтобы вы не использовали импорт напрямую

    const apiCall = (url, {fetchFn = fetch}) => fetchFn(url);
    
    describe("apiCall", () => {
      it("should call fetch with url", () => {
        const fetchFn = sinon.spy();
        const EXPECTED_URL = "URL";
    
        apiCall(EXPECTED_URL, {fetchFn});
    
        expect(fetchFn.firstCall.args).to.deep.equal([EXPECTED_URL]);
      })
    });
    
  • Перехватить запрос и утвердить ответ (кажется, это то, что делает fetch-mock, но я предпочитаю nock поскольку документация намного лучше).

    describe("apiCall", () => {
      it("should call fetch with url", () => {
        const EXPECTED_URL = "URL";
        const EXPECTED_RESPONSE = 'domain matched';
    
        var scope = nock(EXPECTED_URL)
        .get('/resource')
        .reply(200, EXPECTED_RESPONSE);
    
        return expect(apiCall(EXPECTED_URL)).to.equal(EXPECTED_RESPONSE);
      })
    });
    

Ответ 4

Почему бы не экспортировать функцию-создатель в файл Action.js, который получит метод извлечения, а затем вернет фактический apiCaller:

Action.js

// this export allows you to setup an apiCaller
// with a different fetcher than in the global scope 
export const makeApiCaller = (fetch) => (url, opts) => fetch(url, opts);
// this export is your default apiCaller that uses cross-fetch
export const apiCall = makeApiCaller(fetch);

Затем в ваших тестах вы можете где-то создать экземпляр своего ApiCaller, например. в before:

Action.test.js

import fetchMock from 'fetch-mock';
import { makeapiCaller } from './Action';

fetchMock.get('*', { hello: 'world' });

// ...

let apiCall; 
before(() {
  apiCall = makeApiCaller(fetch); // injects mocked fetch 
});

describe('actions', () => {
    apiCall('/foo/bar').then(
        response => {
            console.log(response)
        })
})

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