如何多次声明stubbed的提取

使用proxyquire,sinon和mocha。

我可以在第一次调用fetch时存根。 但是在recursion调用的第二次调用中,我不能断言它。 从输出结果看,在testing结束之前,断言可能会运行。 在断言之后,您将会看到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() 

testing:

 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() }); }); 

  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) 

更新后的版本

@dman,因为你更新了你的testing用例,我欠你一个更新的答案。 虽然有所改变,但情景仍然是非正统的 – 即使你知道它就在你面前,你似乎也想在某种意义上忽视“重力法则”。

我会尽可能地描述。 你有两个function正在做deviseasynchronous的东西。 a()顺序调用b() – 顺便说一下,这不是recursion 。 这两个function在完成/失败时都不会通知他们的呼叫者,即他们被视为“ 火再忘”

现在,让我们来看看你的testing场景。 你创build3个存根。 其中两个parsing为string,另一个使用Promise.all()结合执行。 接下来,您代理“node-fetch”模块

 proxy('./updated', { 'node-fetch': fetch }); 

使用存根来返回存根1和2的联合执行。现在,如果在任一函数中打印出fetch的parsing值,您将看到,而不是一个string,它是一个存根的数组。

 function a () { console.log('function a runs'); fetch('http://localhost') .then((e) => { console.log('first fetch', e); b(); }) .catch((e) => { console.log('error'); }); } 

我猜这不是预期的输出。 但是,让我们继续前进,因为这不会影响你的testing。 接下来,您已经将断言与done()语句一起添加。

 fetch.should.have.been.callCount(2); done(); 

这里的问题是不pipe你使用done()还是不使用,效果都是一样的。 您正在同步模式下执行您的scheme。 当然在这种情况下,断言总是会失败。 但重要的是要理解为什么

所以,我们重写您的场景以模拟您想validation的行为的asynchronous性质。

 '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); }); }); 

正如你所看到的,唯一的变化就是将断言封装在一个定时器模块中。 没什么 – 只要等待10毫秒,然后断言。 现在testing通过了预期。 为什么?

那么,对我来说,这是非常简单的。 你想testing2个顺序执行的asynchronous函数,仍然在同步模式下运行你的断言。 这听起来很酷,但它不会发生:)所以你有2个select:

  • 让你的函数在完成时通知调用者,然后以真正的asynchronous模式运行你的断言
  • 用非正统的技巧模仿事物的asynchronous性

基于原始testing场景进行回复

可以办到。 我重新考虑了你提供的文件,以便可以执行。

index.js

 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); }); }); }; 

alerts.js

 const fetch = require('node-fetch'); module.exports.sendAlert = function () { return new Promise((resolve, reject) => { fetch('https://localhost') .then(function () { resolve(); }).catch((e) => { reject(e); }); }); }; 

test.js

 '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)); }); }); 

当谈到良好的unit testing实践时,你要做的事情虽然有点不正统。 尽pipeproxyquire支持这种通过称为全局覆盖的function的存根模式,但是在这里解释了为什么任何人在进入这条path之前都要三思而后行。

为了让您的示例通过testing,您只需要添加一个额外的属性到名为@global的Sinon存根,并将其设置为true。 这个标志覆盖了require()caching机制,并且使用提供的stub,不pipe调用哪个模块。

所以,虽然你所要求的是可以完成的,但我不得不同意那些评论你的问题的用户,这不应该被用来作为构build你的testing的正确方法。

这也是使用Promise.all()来做到这一点的另一种方法。

注意:如果使用fetch的json方法,并且需要在resolve()传递数据中的数据,这将不起作用。 它只会在解决时通过存根。 但是,它会断言被调用的次数。

 describe('fetch test demo', () => { it('fetch should of called twice', () => { let fetchStub = sinon.stub(); let fetchStub2 = sinon.stub(); let fetch = sinon.stub(); fetchStub.returns(Promise.resolve('hello')); fetchStub2.returns(Promise.resolve('hi')); var promises = [ fetchStub, fetchStub2 ] var promise = Promise.all(promises); fetch.returns(promise); proxy('../foobar', { 'node-fetch': fetch }); return promise.then(() => { fetch.should.have.callCount(2); }); }); });