Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

在 es6 中 sinon 的正确打开方式 #8

Open
soda-x opened this issue Oct 19, 2017 · 6 comments
Open

在 es6 中 sinon 的正确打开方式 #8

soda-x opened this issue Oct 19, 2017 · 6 comments

Comments

@soda-x
Copy link
Owner

soda-x commented Oct 19, 2017

最近一周在补前人留下的测试用例,然后碰到了一个 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 下

无奈之下我选择了最偷懒的方式把 addmultiplication 都修改为 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 被正确调用了?

@ziluo
Copy link

ziluo commented Oct 24, 2017

最近挺高产的,小JJ

@soda-x
Copy link
Owner Author

soda-x commented Oct 24, 2017

@ziluo 课后习题做了么 ☺

@ziluo
Copy link

ziluo commented Oct 24, 2017

示例代码里面build没定义啊 👻

@soda-x
Copy link
Owner Author

soda-x commented Oct 24, 2017

@ziluo fix 了 XD

@cjzcpsyx
Copy link

目前见到写的最明白的es6 dependency stub竟然是一篇中文post,厉害了
是某蚂蚁金服的技术大佬么 😃

@soda-x
Copy link
Owner Author

soda-x commented Apr 9, 2018

@cjzcpsyx 是否有意来我厂 😁

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants