Javascript 如何将从外部作用域调用的方法存根到被测试的函数?

Javascript 如何将从外部作用域调用的方法存根到被测试的函数?,javascript,node.js,mocha.js,sinon,stubbing,Javascript,Node.js,Mocha.js,Sinon,Stubbing,我有一个Redis客户机,它是使用node\u Redis库()创建的: 我想测试一个方法,其目的是设置一个值并将其发布到Redis,因此我想测试以确保根据我的期望调用或不调用set和publish方法。棘手的是,我希望这个测试能够在不需要启动Redis服务器实例的情况下运行,因此我不能仅仅创建客户端,因为如果它无法检测到Redis,它将抛出错误。因此,我需要存根createClient()方法 示例方法: // require('redis').createClient(port, ip) i

我有一个Redis客户机,它是使用node\u Redis库()创建的:

我想测试一个方法,其目的是设置一个值并将其发布到Redis,因此我想测试以确保根据我的期望调用或不调用
set
publish
方法。棘手的是,我希望这个测试能够在不需要启动Redis服务器实例的情况下运行,因此我不能仅仅创建客户端,因为如果它无法检测到Redis,它将抛出错误。因此,我需要存根
createClient()
方法

示例方法:

// require('redis').createClient(port, ip) is called once and the 'client' object is used globally in my module.
module.exports.updateRedis = function (key, oldVal, newVal) {
  if (oldVal != newVal) {
    client.set(key, newVal);
    client.publish(key + "/notify", newVal);
  }
};
我尝试了几种方法来测试set和publish是否使用预期的键和值调用,但都没有成功。如果我试图监视这些方法,我可以通过运行调试器判断是否调用了我的方法,但是calledOnce对我来说没有被标记为true。如果我存根
createClient
方法以返回假客户端,例如:

{
  set: function () { return 'OK'; },
  publish: function () { return 1; }
}
测试中的方法似乎没有使用假客户端

现在,我的测试如下所示:

var key, newVal, oldVal, client, redis;

before(function () {
  key = 'key';
  newVal = 'value';
  oldVal = 'different-value';
  client = {
    set: function () { return 'OK'; },
    publish: function () { return 1; }
  }
  redis = require('redis');
  sinon.stub(redis, 'createClient').returns(client);

  sinon.spy(client, 'set');
  sinon.spy(client, 'publish');
});

after(function () {
  redis.createClient.restore();
});

it('sets and publishes the new value in Redis', function (done) {
  myModule.updateRedis(key, oldVal, newVal);

  expect(client.set.calledOnce).to.equal(true);
  expect(client.publish.calledOnce).to.equal(true);

  done();
});
上面的代码给了我一个断言错误(我正在使用Chai)

AssertionError:预期false等于true

我在控制台日志中也发现了这个错误,这表明当方法实际运行时,客户端没有被终止

连接到redis时出错[错误:就绪检查失败:redis连接从结束事件退出。]

更新

此后,我在我的测试套件最外层的descripe块中尝试了剔除
createClient
方法(使用
before
函数,以便它在测试之前运行),结果是相同的-当测试实际运行我的函数时,它似乎没有返回假客户端

我也试着把我的间谍放在顶级描述的
之前的
,但没有用

我注意到,当我杀死我的Redis服务器时,我从Redis收到连接错误消息,尽管这是(目前)唯一涉及使用Redis客户端的代码的测试。我知道这是因为我在NodeJS服务器启动时创建了客户端,Mocha在执行测试时将创建服务器应用程序的实例。我现在假设,这一点没有得到正确的处理是因为它不仅仅是一个需求,而是在应用程序启动时调用
createClient()
函数,而不是在调用正在测试的函数时。我觉得仍然应该有一种方法来存根这个依赖项,即使它是全局的,并且被存根的函数在我的测试函数之前被调用


其他可能有用的信息:我正在使用Gulp任务运行程序,但我不知道这会如何影响测试的运行。

在我的测试套件中创建
应用程序之前,我最终使用
fakeredis
()删除Redis客户端,如下所示:

var redis = require('fakeredis'),
    konfig = require('konfig'),
    redisClient = redis.createClient(konfig.redis.port, konfig.redis.host);

sinon.stub(require('redis'), 'createClient').returns(redisClient);

var app = require('../../app.js'),
//... and so on
然后我就可以正常使用sinon.spy了:

describe('some case I want to test' function () {
  before(function () {
    //...
    sinon.spy(redisClient, 'set');
  });

  after(function () {
    redisClient.set.restore();
  });

  it('should behave some way', function () {
    expect(redisClient.set.called).to.equal(true);
  });
});
还可以在客户机上模拟和存根,我发现这比使用
redisErrorClient
在回调中测试Redis错误处理更好

很明显,我不得不求助于Redis的模拟库来实现这一点,因为只要redisClient()方法在被测函数的外部作用域中被调用,Sinon就无法将其删除。这是有道理的,但这是一个恼人的限制

describe('some case I want to test' function () {
  before(function () {
    //...
    sinon.spy(redisClient, 'set');
  });

  after(function () {
    redisClient.set.restore();
  });

  it('should behave some way', function () {
    expect(redisClient.set.called).to.equal(true);
  });
});