在 es6 中 sinon 的正确打开方式
soda-x opened this issue · 6 comments
最近一周在补前人留下的测试用例,然后碰到了一个 sinon 的使用问题困扰了一整天,特做此记录。
文章的前提是:你对 sinon 已经有了初步的使用经验。
// src.js
function add(a, b) {
return a + b;
}
function multiplication(a, b) {
return a * b;
}
export function complex(a, b) {
console.error('此处为 error 发生出');
return add(a, b) + multiplication(a, b);
}
export default function division(a, b) {
return add(a, b)/multiplication(a, b);
}
//test.js
import test from 'ava'; // eslint-disable-line
import sinon from 'sinon'; // eslint-disable-line
import { complex }, division from '../src';
let sandbox;
test.before(() => {
sandbox = sinon.sandbox.create();
});
test.after(() => {
sandbox.restore();
});
test('complex', (t) => {
sandbox.stub(console, 'error');
// how to stub add or multiplication ?
});
碰到第一个问题是:如何 stub 非 export 的方法呢 ?另外 stub 的 api 要求,method 需要挂载在相应的
object 下
无奈之下我选择了最偷懒的方式把 add
和 multiplication
都修改为 export 的方法,使用了 import * 的方式
即修改为了
// src.js
export function add(a, b) {
return a + b;
}
export function multiplication(a, b) {
return a * b;
}
export function complex(a, b) {
console.error('此处为 error 发生出');
return add(a, b) + multiplication(a, b);
}
export default function division(a, b) {
return add(a, b)/multiplication(a, b);
}
//test.js
import test from 'ava'; // eslint-disable-line
import sinon from 'sinon'; // eslint-disable-line
import * as math from '../src';
let sandbox;
test.before(() => {
sandbox = sinon.sandbox.create();
});
test.after(() => {
sandbox.restore();
});
test('complex', (t) => {
sandbox.stub(console, 'error');
sandbox.stub(math, 'add').returns(2);
sandbox.stub(math, 'multiplication').returns(3);
t.true(console.error.called);
t.is(math.complex(2, 3), 5);
// 用例挂了 t.is(11, 5);
});
偷懒方式破灭,发现 math.complex(2, 3) 返回的是 11,也就是 stub math add 和 multiplication 并没有生效!但是关键是 console.error 被正常改写。到底发生了什么!所以我猜想 stub 的 add 和 multiplication 方法并不是 complex 方式调用的 add 和 multiplication
由于并不清楚发生了什么,所以猜测是不是因为引用关系的问题
所以我开始尝试,改写 src.js 尽量保证引用关系
let math;
function add(a, b) {
return a + b;
}
function multiplication(a, b) {
return a * b;
}
function complex(a, b) {
console.error('此处为 error 发生出');
process.exit(1);
return math.add(a, b) + math.multiplication(a, b);
}
math = {
add,
multiplication,
complex,
};
export { math };
然而答案居然是成功了!!!!但回过头必须要思考的事情是:1.这种处理方案并不完美,原因在于为了写测试需要变更源码本身相对优雅的写法,同时会暴露无关的内部函数 2. 为什么 直接 export 到具体的 function 不行,但是 export 到 对象就行了,这或许并不是引用关系的问题。
带着这个思考,我往这个方向 google 了下,非常有意思我发现了在 stackoverflow 上的提问 https://stackoverflow.com/questions/35240469/how-to-mock-the-imports-of-an-es6-module
其中在非常不显眼的地方我居然看到了问题最最最关键的内容,
@carpeliam This wont work with the ES6 module spec where the imports are readonly.
import 的内容是 readonly 的!!!!!!!但是其内部 child 不是 readonly 的!!!!!!!至此豁然开朗!!!!
接下来我就开始想,如果说这是因为 spec 的原因导致,那么万能的 babel 解决这个问题肯定易如反掌,所以我开始尝试搜索这方面的 babel-plugin 。结果当然是 wala babel-plugin-rewire
因为找对了方向,所以问题的解决方式也越合规。其中认为最合适的是how to stub ES6 module dependencies。
最佳实践
// src.js 不需要对 源码 文件作出任何的调整
function add(a, b) {
return a + b;
}
function multiplication(a, b) {
return a * b;
}
export function complex(a, b) {
console.error('此处为 error 发生出');
return add(a, b) + multiplication(a, b);
}
export default function division(a, b) {
return add(a, b)/multiplication(a, b);
}
import test from 'ava'; // eslint-disable-line
import sinon from 'sinon'; // eslint-disable-line
import * as math from '../src';
let sandbox;
const rewire = (module, methodName, method) => {
module.__Rewire__(methodName, method);
return method;
};
test.before(() => {
sandbox = sinon.sandbox.create();
});
test.after(() => {
sandbox.restore();
});
test('complex', (t) => {
sandbox.stub(console, 'error');
rewire(math, 'add', sandbox.stub())
.returns(2);
rewire(math, 'multiplication', sandbox.stub())
.returns(3);
t.true(console.error.called);
t.is(math.complex(2, 3), 5);
});
补充课外题
当如果 src.js
中我们 import 了 一个 util 的方法
// src.js
import { chalk } from 'util';
export default function log() {
console.log(chalk.yellow('yellow log'));
}
请问如何测试 chalk.yellow 被正确调用了?
最近挺高产的,小JJ
示例代码里面build
没定义啊 👻
目前见到写的最明白的es6 dependency stub竟然是一篇中文post,厉害了
是某蚂蚁金服的技术大佬么 😃