Бывали случаи, когда вы хотели протестировать функцию, у которой есть зависимости. Вы бы заглушили эти зависимости, чтобы заставить свой код перейти по определенному пути.
Возьмем для примера эту функцию.
// kitchen.js import { getIngredients } from './groceries' import { blend } from './kitchen-utils' export const cook = () => { const ingredients = getIngredients() return ingredients.length ? blend(ingredients) : 'There was nothing to eat :(((' }
Давайте представим getIngrediants
как функцию, которая извлекает данные из произвольного API. Вы, наверное, не хотите использовать API каждый раз, когда запускаете тест, не так ли? Это приведет к тому, что выполнение ваших модульных… тестов займет довольно много времени.
Итак, вы узнали, что есть вызов библиотеки Sinon.JS
, который может спасти ваши задницы. И вы были счастливы, что на выполнение вашего теста уходит значительно меньше времени.
import * as groceries from './groceries' // import other neccessary stuffs here describe('cook', () => { describe('when there was no ingredient', () => { it('mama would be angry', () => { sinon.stub(groceries, 'getIngredients') .returns([]) const food = cook() expect(food).toEqual('There was nothing to eat :(((') }) }) })
Но $ h1t возникает, когда вы тестировали другую функцию в том же файле, где был определен cook
export const cook = () => { /* blabla */ } export const serve = () => { const food = cook() return food.isDelicious ? food : 'There was nothing to serve :(((' }
Потому что вы правильно cook
заглушили (мысленно) и весь вечер выясняете, почему он не ведет себя так, как вы хотите.
import * as kitchen from 'kitchen' describe('serve', () => { describe('when food is delicious, () => { it('should serve me that yummy food', () => { const mockFood = { isDelicious: true } sinon.stub(kitchen, 'cook').returns(mockFood) const food = serve() expect(food).toEqual(mockFood) // this ain't working // your test runs actual `cook` // implementation }) }) })
Короче говоря, вы заглушали экспортированное значение cook
, но реализация в serve
вызывает cook
напрямую, так что это будет преобразовано в функцию cook
, которая была определена в kitchen.js
.
Итак, как мы могли это обойти?
Мы могли бы изменить способ вызова cook
, чтобы убедиться, что он сначала будет вызывать из экспортированного значения.
const cook = () => { /* blabla */ } const serve = () => { const food = module.cook() // blabla } const module = { cook, serve } export module
Но я не хочу писать вот так экспорт, я так ленив, что хотел просто написать export const a = whatEver
Хорошо, все равно есть способ исправить это, сделав это
export const cook = () => { /* blabla */ } export const serve = () => { const food = exports.cook() // blabla }
СВЯТОЙ МОЛИЙ !!! Оно работает!!!
Ага-ага. Потому что exports
будет относиться к области, в которую был экспортирован этот модуль. Итак, когда вы снова вставляете cook
в kitchen
, вместо этого будет использоваться метод заглушки.
Заявление об ограничении ответственности
Я сам обнаружил этот странный трюк и, честно говоря, не уверен, будет ли он работать в разных средах или нет, особенно если вы используете Babel с разными конфигурациями. Но если вы это сделаете, дайте мне знать. Мир