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