使用 Sinon 在同一文件中存根方法
Stubbing method in same file using 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()
一些重组可以使这项工作。
我使用了 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);
输出
mocked return
1
虽然上面的方法确实有效,但它绝对是一种解决方法,因为我的 linter 很快就通知了。
我最终分离模块并使用 proxyquire。该库允许您轻松地将任何/所有导出替换为您选择的导出,包括 sinon 存根间谍或模拟。例如:
在b.js
export const fnB = () => 'hey there!';
在a.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'
上面提到的方法(使用factory
收集函数)效果很好;但是,eslint 不喜欢使用尚未声明的 variable/function。因此我建议稍微修改一下:
// 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 result;
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;
我更喜欢这种使用 "collector" 在同一个文件中调用函数的技术,因为为您编写的每个函数创建单独的文件并不总是一个好主意。通常,我发现我会创建许多相关的实用函数,以使我的代码更具可读性、可重用性和可组合性;将每个函数放在一个单独的文件中会使代码稍微难以理解,因为 reader 如果不在不同文件之间跳转就无法看到这些函数的定义。
我遇到了同样的问题,找到了一种方法。您可以将 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。
我正在尝试对文件中的一个函数进行单元测试,同时对同一文件中的另一个函数进行存根,但是没有应用模拟,而是调用了真实的方法。这是一个例子:
// 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()
一些重组可以使这项工作。
我使用了 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);
输出
mocked return
1
虽然上面的方法确实有效,但它绝对是一种解决方法,因为我的 linter 很快就通知了。
我最终分离模块并使用 proxyquire。该库允许您轻松地将任何/所有导出替换为您选择的导出,包括 sinon 存根间谍或模拟。例如:
在b.js
export const fnB = () => 'hey there!';
在a.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'
上面提到的方法(使用factory
收集函数)效果很好;但是,eslint 不喜欢使用尚未声明的 variable/function。因此我建议稍微修改一下:
// 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 result;
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;
我更喜欢这种使用 "collector" 在同一个文件中调用函数的技术,因为为您编写的每个函数创建单独的文件并不总是一个好主意。通常,我发现我会创建许多相关的实用函数,以使我的代码更具可读性、可重用性和可组合性;将每个函数放在一个单独的文件中会使代码稍微难以理解,因为 reader 如果不在不同文件之间跳转就无法看到这些函数的定义。
我遇到了同样的问题,找到了一种方法。您可以将 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。