#javascript #unit-testing #reactjs #redux #sinon
#javascript #модульное тестирование #reactjs #redux #sinon
Вопрос:
Я пытаюсь выполнить модульное тестирование функции в файле, одновременно заглушая другую функцию в ТОМ ЖЕ файле, но макет не применяется, и вызывается реальный метод. Вот пример:
// file: 'foo.js'
export function a() {
// .....
}
export function b() {
let stuff = a(); // call a
// ...do stuff
}
И мой тест:
import * as actions from 'foo';
const aStub = sinon.stub(actions, 'a').returns('mocked return');
actions.b(); // b() is executed, which calls a() instead of the expected aStub()
Комментарии:
1. Как общее эмпирическое правило, вы не должны заглушать / издеваться над функцией в том же модуле, который вы тестируете. Если вы обнаружите, что вам нужно это сделать, это хороший признак того, что две функции на самом деле должны быть в разных модулях. Конечно, правила созданы для того, чтобы их нарушать, и есть ситуации, когда это может быть нормально, но в целом вам следует избегать.
2. @AndrewEisenberg В настоящее время я нахожусь в такой ситуации и вижу, что все еще немного сложно имитировать функцию в том же модуле, который я тестирую. Не могли бы вы пояснить, почему, как правило, я должен разделить две функции на отдельные модули?
Ответ №1:
Некоторая реструктуризация может заставить это работать.
Я использовал синтаксис CommonJS. Должен работать таким же образом и в ES6.
foo.js
const factory = {
a,
b,
}
function a() {
return 2;
}
function b() {
return factory.a();
}
module.exports = factory;
test.js
const ser = require('./foo');
const sinon = require('sinon');
const aStub = sinon.stub(ser, 'a').returns('mocked return');
console.log(ser.b());
console.log(aStub.callCount);
Вывод
издевательский возврат
1
Комментарии:
1. не могли бы вы привести пример, когда функции a и b получают некоторый аргумент и используют синтаксис экспорта es6
2. Любое объяснение?
3. почему это работает?
4. @gaurav5430 что вы имеете в виду? Вы находите какие-либо проблемы?
5. нет, я имею в виду, почему эта реструктуризация работает, а исходный код — нет
Ответ №2:
Хотя вышеупомянутое работает, это определенно обходной путь, поскольку мой линтер быстро сообщил.
В итоге я разделил модули и использовал proxyquire. Эта библиотека позволяет вам легко заменять любые / все экспортные файлы на те, которые вы выбираете, включая sinon stub spy или mocks. например :
в b.js
export const fnB = () => 'hey there!';
в .js
import { fbB } from 'b.js';
export const fnA = () => fbB();
в a.test.js
import { noCallThru } from 'proxyquire';
const proxyquireStrict = noCallThru();
const stubB = stub().returns('forced result');
const moduleA = proxyquireStrict('a.js', {
'b.js' : { fnB: stubB }
}).fnA;
console.log(fnA()); // 'forced result'
Комментарии:
1. Разве это не обходной путь? Что, если неправильно разделять модули?
Ответ №3:
Метод, упомянутый выше (с использованием a factory
для сбора функций), работает хорошо; однако eslint не понравится использование переменной / функции, которая еще не была объявлена. Поэтому я бы рекомендовал небольшую модификацию:
// my-functions.js
export const factory = {};
export const funcA = () => {
return facory.funcB();
};
factory.funcA = funcA;
export const funcB = () => true;
factory.funcB = funcB;
// my-functions-test.js
import {factory, funcA, funcB} from './path/to/my-functions';
describe('MyFunctions | funcA', () => {
test('returns result from funcB call', () => {
const funcBStub = sinon.stub(factory, 'funcB').returns(false);
// Test that the function does not throw errors
let resu<
expect(() => (result = funcA())).not.toThrow();
// Test that the return value is that of the mock rather than the original function
expect(result).toEqual(false);
// Test that the stub was called
expect(funcBStub.called).toEqual(true);
});
});
// Don't forget to test funcB independently ;)
Важным отличием является добавление функций в файле в том factory
виде, в котором они определены, чтобы избежать нарушения правил eslint. Единственный случай, когда это может вызвать проблемы, — это если вы попытались вызвать одну из этих функций в том же файле до того, как все они были определены. Пример:
// my-functions-1.js
export const factory = {};
export const funcA = () => {
factory.funcB();
};
factory.funcA = funcA;
// Since the code execution runs from top to bottom, calling funcA here means that funcB has not yet been added to factory
funcA(); // Throws an error since factory.funcB() is not a function (yet)
export const funcB = () => true;
factory.funcB = funcB;
Я предпочитаю этот метод использования «сборщика» для вызова функций в одном файле, поскольку не всегда хорошая идея создавать отдельные файлы для КАЖДОЙ функции, которую вы пишете. Часто я обнаруживаю, что создаю множество связанных служебных функций, чтобы сделать мой код более читаемым, многоразовым и составным; помещение каждой функции в отдельный файл немного усложнило бы понимание кода, поскольку читатель не мог видеть определения этих функций, не переходя между разными файлами.
Ответ №4:
Я столкнулся с той же проблемой и нашел один метод. Вы можете изменить свой foo.js файл к этому:
// file: 'foo.js'
export function a() {
// .....
}
export function b() {
let stuff = exports.a(); // using "exports." to call a
// ...do stuff
}
Пожалуйста, обратитесь к https://codeburst.io/stub-dependencies-with-sinon-js-259ac12379b9 .