如何为测试函数中被外部函数调用的方法创建存根?

4

我有一个使用node_redis库(https://github.com/NodeRedis/node_redis)创建的Redis客户端:

var client = require('redis').createClient(6379, 'localhost');

我有一个需要测试的方法,它的目的是将值设置并发布到Redis,因此我想测试以确保setpublish方法是否按照我的期望被调用或未被调用。棘手的问题是,我希望这个测试可以在不启动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。如果我使用stub方法来返回一个假客户端,例如: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: expected false to equal true ```
我在控制台日志中也收到了这个错误,这表明当方法实际运行时,客户端没有被截取。
``` Error connecting to redis [Error: Ready check failed: Redis connection gone from end event.] ```
更新:
我已经尝试在我的测试套件的最外层describe块中截取了createClient方法(使用before函数使其在我的测试之前运行),但结果相同 - 当测试实际运行我的函数时,它似乎没有返回假客户端。
我还尝试将我的spy放在顶级描述的before中,但没有效果。
我注意到当我杀死Redis服务器时,我会从Redis获得连接错误消息,即使这是唯一触摸使用Redis客户端的任何代码的测试。我知道这是因为我在此NodeJS服务器启动时创建客户端,而Mocha将在执行测试时创建服务器应用程序的实例。我现在认为之所以无法正确地进行存根是因为它不仅仅是要求,而且在应用程序启动时调用createClient()函数,而不是在我调用正在测试的函数时。我感觉仍然应该有一种方法来存根这个依赖项,即使它是全局的,并且在我的测试函数之前调用被存根的函数。
其他有用的信息:我正在使用Gulp任务运行器 - 但我不知道这应该如何影响测试运行。
1个回答

3

在我的测试套件中创建app之前,我最终使用fakeredishttps://github.com/hdachev/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);
  });
});

在客户端上模拟和存根化也是可能的,我发现这比使用提供给测试Redis错误处理回调的redisErrorClient更好。

显然,我不得不利用一个Redis模拟库来做到这一点,因为只要在测试函数的外部范围内调用redisClient()方法,Sinon就无法对其进行存根。这是有道理的,但这是一个令人烦恼的限制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接