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

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

действие.js

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

действие.тест.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' line)?

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

4 ответов


Если ваш проект является проектом 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);
  });

});

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


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


есть несколько способов, вы можете подойти к этой проблеме

  1. Вы можете модули заглушки без инъекции зависимостей с помощью Sinon.

  2. используйте lib под названием rewire издеваться над импортированными методами в процедурных звонки

  3. повторно напишите исходную функцию, чтобы вы не использовали импорт напрямую

    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]);
      })
    });
    
  4. перехватить запрос и утверждать на ответ (это, похоже, то, что fetch-mock делает, однако я предпочитаю НОК поскольку документация намного лучше).

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

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

действие.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:

действие.тест.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 подпись функции (как предложено в другом ответе) и, таким образом, оставаться обратно совместимыми.