如何多次断言存根获取 [英] How to assert stubbed fetch more than once

查看:32
本文介绍了如何多次断言存根获取的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用 proxyquire、sinon 和 mocha.

Using proxyquire, sinon, and mocha.

我能够在第一次调用 fetch 时存根 fetch.但是在递归的第二个 fetch 调用中,我无法断言它.从输出来看,断言可能会在测试完成之前运行.您将在断言后通过 second fetch 控制台看到这一点.

I am able to stub fetch on the first call of fetch. But on the second fetch call, which is recursive, I am not able to assert it. From the output, it looks like the assertion may run before the test finishes. You will see this with second fetch console out after assertion.

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

<小时>

测试:

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,既然你更新了你的测试用例,我欠你一个更新的答案.尽管经过改写,但该场景仍然是非正统的 - 即使您知道它就在您面前,您似乎也想在某种意义上忽略万有引力定律".

Updated version

@dman, since you updated your test case I owe you an updated answer. Although rephrased, the scenario is still unorthodox - it seems like you want to ignore in a sense the 'law of gravity' even though you know it's right there in front of you.

我会尽量做到描述性.您有两个函数按照设计执行异步.a() 依次调用 b() - 顺便说一下,这不是 递归.这两个函数在完成/失败时都不会通知它们的调用者,即它们被视为即发即弃.

I'll try to be as descriptive as possible. You have two functions which are doing async stuff by design. a() calls b() sequentially - by the way this is not recursion. Both functions do not notify their callers upon completion / failure, i.e. they are treated as fire-and-forget.

现在,让我们看看您的测试场景.您创建了 3 个存根.其中两个解析为一个字符串,一个使用 Promise.all() 组合它们的执行.接下来,代理node-fetch"模块

Now, let's have a look at your test scenario. You create 3 stubs. Two of them resolve to a string and one combining their execution using Promise.all(). Next, you proxy the 'node-fetch' module

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

使用返回存根 1 & 组合执行的存根2. 现在,如果您在任一函数中打印出 fetch 的解析值,您将看到它不是一个字符串,而是一个存根数组.

using the stub that returns the combined execution of stubs 1 & 2. Now, if you print out the resolved value of fetch in either function, you will see that instead of a string it's an array of stubs.

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

我猜这不是预期的输出.但是让我们继续前进,因为这无论如何都不会杀死您的测试.接下来,您已将断言与 done() 语句一起添加.

Which I guess is not the intended output. But let's move over as this is not killing your test anyway. Next, you have added the assertion together with the done() statement.

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

这里的问题是,无论您是否使用 done(),效果都是一样的.您正在同步模式下执行您的场景.当然在这种情况下,断言总是会失败.但这里重要的是要了解为什么.

The issue here is that whether you are using done() or not, the effect would be exactly the same. You are executing your scenario in sync mode. Of course in this case, the assertion will always fail. But the important thing here is to understand why.

因此,让我们重写您的场景,以模仿您要验证的行为的异步性质.

So, let's rewrite your scenario to mimic the async nature of the behavior you want to validate.

'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 毫秒,然后断言.现在测试按预期通过了.为什么?

As you can see, the only change made was wrapping the assertion within a timer block. Nothing much - just wait for 10ms and then assert. Now the test passes as expected. Why?

嗯,对我来说这很简单.您想要测试 2 个按顺序执行的 async 函数,并且仍然在 sync 模式下运行您的断言.这听起来很酷,但它不会发生:) 所以你有两个选择:

Well, to me it's pretty straightforward. You want to test 2 sequentially executed async functions and still run your assertions in sync mode. That sounds cool, but it's not gonna happen :) So you have 2 options:

  • 让您的函数在完成时通知调用者,然后以真正的异步模式运行您的断言
  • 使用非正统技术模仿事物的异步性质

这是可以的.我已经重新分解了您提供的文件,以便可以执行.

It can be done. I've re-factored your provided files a bit so that can be executed.

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

  });

});

尽管涉及到,您尝试做的事情有点不正统良好的单元测试实践.虽然 proxyquire 支持这个通过称为全局覆盖的功能存根的模式,它是在这里解释 为什么有人在下山前要三思这条路.

What you're trying to do though is a bit unorthodox when it comes to good unit testing practices. Although proxyquire supports this mode of stubbing through a feature called global overrides, it is explained here why should anyone think twice before going down this path.

为了使您的示例通过测试,您只需要添加一个为名为 @global 的 Sinon 存根添加额外属性并将其设置为真的.该标志覆盖了 require() 缓存机制和无论从哪个模块调用,都使用提供的存根.

In order to make your example pass the test, you just need to add an extra attribute to the Sinon stub called @global and set it to true. This flag overrides the require() caching mechanism and uses the provided stub no matter which module is called from.

所以,虽然你的要求可以做到,但我不得不同意评论您问题的用户,这不应该是作为构建测试的正确方法.

So, although what you're asking can be done I will have to agree with the users that commented your question, that this should not be adopted as a proper way of structuring your tests.

这篇关于如何多次断言存根获取的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆