dma*_*man 7 javascript unit-testing node.js sinon proxyquire
使用proxyquire,sinon和mocha.
我能够在第一次调用fetch时获取存根.但是在第二次提取调用中,这是递归的,我无法断言它.从输出看,断言可能在测试结束之前运行.second fetch在断言后你会看到控制台输出.
index.js
var fetch = require('node-fetch');
function a() {
console.log('function a runs');
fetch('https://www.google.com')
.then((e) => {
console.log('first fetch');
b();
})
.catch((e)=> {
console.log('error')
});
}
function b() {
fetch('https://www.google.com')
.then((e) => {
console.log('second fetch');
})
.catch((e)=> {
console.log('error')
});
}
a()
Run Code Online (Sandbox Code Playgroud)
测试:
describe('fetch test demo', ()=> {
it('fetch should of called twice', (done)=> {
fetchStub = sinon.stub();
fetchStub2 = sinon.stub();
fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [ fetchStub, fetchStub2 ]
fetch.returns(Promise.all(promises));
proxy('../index', {
'node-fetch': fetch
});
fetch.should.have.been.callCount(2);
done()
});
});
Run Code Online (Sandbox Code Playgroud)
fetch test demo
function a runs
1) fetch should of called twice
first fetch
second fetch
lifx alert test
- fetch should of called three times
when rain change is over 50%
- should run fetch twice
0 passing (78ms)
2 pending
1 failing
1) fetch test demo fetch should of called twice:
expected stub to have been called exactly twice, but it was called once
stub(https://www.google.com) => [Promise] { } at a (/home/one/github/lifx-weather/foobar.js:5:3)
AssertionError: expected stub to have been called exactly twice, but it was called once
stub(https://www.google.com) => [Promise] { } at a (foobar.js:5:3)
at Context.it (test/bar.js:22:28)
Run Code Online (Sandbox Code Playgroud)
@dman,自从您更新了测试用例以来,我欠您一个更新的答案。虽然经过重新表述,但这个场景仍然是非正统的——在某种意义上,你似乎想忽略“万有引力定律”,尽管你知道它就在你面前。
我会尽可能地进行描述。您有两个按设计 执行异步操作的函数。a()按顺序调用b() - 顺便说一句,这不是递归。这两个函数在完成/失败时都不会通知其调用者,即它们被视为“即发即弃”。
现在,让我们看看您的测试场景。您创建 3 个存根。其中两个解析为字符串,一个使用 组合它们的执行Promise.all()。接下来,您代理“node-fetch”模块
proxy('./updated', {
'node-fetch': fetch
});
Run Code Online (Sandbox Code Playgroud)
使用返回存根 1 和 2 的组合执行的存根。现在,如果您在任一函数中打印出fetch的解析值,您将看到它不是字符串,而是存根数组。
function a () {
console.log('function a runs');
fetch('http://localhost')
.then((e) => {
console.log('first fetch', e);
b();
})
.catch((e) => {
console.log('error');
});
}
Run Code Online (Sandbox Code Playgroud)
我猜这不是预期的输出。但让我们继续吧,因为无论如何这不会杀死你的测试。接下来,您将断言与did()语句一起添加。
fetch.should.have.been.callCount(2);
done();
Run Code Online (Sandbox Code Playgroud)
这里的问题是,无论你是否使用done(),效果都是完全相同的。您正在同步模式下执行您的场景。当然,在这种情况下,断言总是会失败。但这里重要的是了解原因。
因此,让我们重写您的场景以模仿您要验证的行为的异步性质。
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
chai.use(SinonChai);
chai.should();
const proxy = require('proxyquire');
describe('fetch test demo', () => {
it('fetch should of called twice', (done) => {
var fetchStub = sinon.stub();
var fetchStub2 = sinon.stub();
var fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [fetchStub, fetchStub2];
fetch.returns(Promise.all(promises));
proxy('./updated', {
'node-fetch': fetch
});
setTimeout(() => {
fetch.should.have.been.callCount(2);
done();
}, 10);
});
});
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,所做的唯一更改是将断言包装在计时器块中。没什么大不了的——只需等待 10 毫秒,然后断言即可。现在测试按预期通过了。为什么?
嗯,对我来说这非常简单。您想要测试 2 个顺序执行的异步函数,并且仍然在同步模式下运行您的断言。这听起来很酷,但这不会发生:)所以你有两个选择:
可以办到。我已经对您提供的文件进行了一些重构,以便可以执行。
索引.js
Run Code Online (Sandbox Code Playgroud)const fetch = require('node-fetch'); const sendAlert = require('./alerts').sendAlert; module.exports.init = function () { return new Promise((resolve, reject) => { fetch('https://localhost') .then(function () { sendAlert().then(() => { resolve(); }).catch( e => reject(e) ); }) .catch(e => { reject(e); }); }); };警报.js
Run Code Online (Sandbox Code Playgroud)const fetch = require('node-fetch'); module.exports.sendAlert = function () { return new Promise((resolve, reject) => { fetch('https://localhost') .then(function () { resolve(); }).catch((e) => { reject(e); }); }); };测试.js
Run Code Online (Sandbox Code Playgroud)'use strict'; const chai = require('chai'); const sinon = require('sinon'); const SinonChai = require('sinon-chai'); chai.use(SinonChai); chai.should(); const proxy = require('proxyquire'); describe.only('lifx alert test', () => { it('fetch should of called twice', (done) => { var body = { 'hourly': { data: [{ time: 1493413200, icon: 'clear-day', precipIntensity: 0, precipProbability: 0, ozone: 297.17 }] } }; var response = { json: () => { return body; } }; const fetchStub = sinon.stub(); fetchStub.returns(Promise.resolve(response)); fetchStub['@global'] = true; var stubs = { 'node-fetch': fetchStub }; const p1 = proxy('./index', stubs); p1.init().then(() => { try { fetchStub.should.have.been.calledTwice; done(); } catch (e) { done(e); } }).catch((e) => done(e)); }); });不过,当涉及到良好的单元测试实践时,您尝试做的事情有点非正统。尽管proxyquire通过称为全局覆盖的功能支持这种存根模式,但 这里解释了为什么任何人在走这条路之前都应该三思而后行。
为了使您的示例通过测试,您只需向 Sinon 存根添加一个名为@global的额外属性并将其设置为 true。该标志会覆盖require()缓存机制,并使用提供的存根,无论从哪个模块调用。
因此,尽管您所要求的可以完成,但我必须同意评论您的问题的用户,但这不应该被采用作为构建测试的正确方法。