Stubbing Date.now() and Math.random()



我使用Mocha与Sinon单元测试我的node.js模块。我已经成功地模拟了其他依赖关系(我编写的其他模块),但是我遇到了非纯函数(如Math.random()Date.now())的问题。我已经尝试了以下(简化,使这个问题不是那么本地化),但Math.random()没有存根,因为一个明显的范围问题。Math的实例在测试文件和mymodule.js之间是独立的。

. js

var sinon    = require('sinon'),
    mymodule = require('./mymodule.js'),
    other    = require('./other.js');
describe('MyModule', function() {
    describe('funcThatDependsOnRandom', function() {
        it('should call other.otherFunc with a random num when no num provided', function() {
            sinon.mock(other).expects('otherFunc').withArgs(0.5).once();
            sinon.stub(Math, 'random').returns(0.5);
            funcThatDependsOnRandom(); // called with no args, so should call
                                       // other.otherFunc with random num
            other.verify(); // ensure expectation has been met
        });
    });
});

所以在这个人为的例子中,functThatDependsOnRandom()看起来像:

mymodule.js

var other = require('./other.js');
function funcThatDependsOnRandom(num) {
    if(typeof num === 'undefined') num = Math.random();
    return other.otherFunc(num);
}

是否有可能在这种情况下与Sinon存根Math.random() ?

是的,这是一个老问题,但它是有效的。这里是一个有效的答案,尽管我很想听到如何做得更好的建议。

我在浏览器中处理这个问题的方法是创建一个代理对象。例如,您不能在浏览器中存根窗口对象,因此您可以创建一个名为windowProxy的代理对象。当你想要获取位置时,你在windowProxy中创建一个叫做location的方法,它返回或设置windowLocation。然后,在测试时,模拟windowProxy.location.

你可以用Node.js做同样的事情,但它的工作并不那么简单。简单的说法是,一个模块不能干扰另一个模块的私有命名空间。

解决方案是使用mock模块。在初始化mock之后,如果您调用require()时使用的参数与您告诉mock要mock的参数相匹配,它将允许您覆盖require语句并返回您自己的属性。

UPDATE:我创建了一个功能齐全的代码示例。它在Github上的newz2000/dice-tdd上,可以通过npm获得。/结束更新

文档非常好,所以我建议阅读它们,但这里有一个例子:

创建一个文件randomHelper.js,内容如下:

module.exports.random = function() {
  return Math.random();
}

然后在需要随机数的代码中:

var randomHelper = require('./randomHelper');
console.log('A random number: ' + randomHelper.random() );

一切正常。代理对象的行为方式与Math.random.

相同。

需要注意的是,require语句只接受一个参数'./randomHelper'。我们需要注意这一点。

现在在你的测试中,(我用摩卡和chai为例):

var sinon = require('sinon');
var mockery = require('mockery')
var yourModule; // note that we didn't require() your module, we just declare it here
describe('Testing my module', function() {
  var randomStub; // just declaring this for now
  before(function() {
    mockery.enable({
      warnOnReplace: false,
      warnOnUnregistered: false
    });
    randomStub = sinon.stub().returns(0.99999);
    mockery.registerMock('./randomHelper', randomStub)
    // note that I used the same parameter that I sent in to requirein the module
    // it is important that these match precisely
    yourmodule = require('../yourmodule');
    // note that we're requiring your module here, after mockery is setup
  }
  after(function() {
    mockery.disable();
  }
  it('Should use a random number', function() {
    callCount = randomStub.callCount;
    yourmodule.whatever(); // this is the code that will use Math.random()
    expect(randomStub.callCount).to.equal(callCount + 1);
  }
}

就是这样。在这种情况下,存根将始终返回0.0.99999;你当然可以改变它

通过使用假计时器很容易将Date.now()sinon存根:

伪计时器提供了一个时钟对象来传递时间,它也可以用来控制通过new Date()创建的Date对象;或Date.now ();(如果浏览器支持的话)。

// Arrange
const now = new Date();
const clock = sinon.useFakeTimers(now.getTime());
// Act
// Call you function ...
// Assert
// Make some assertions ...
// Teardown
clock.restore();

您确定问题不在于嘲弄Math吗?这一行似乎没有多大意义:

sinon.mock(other).expects('otherFunc').withArgs(0.5).once();

在一个模块中模拟others,但在另一个模块中使用它。我不认为你会得到mymodule.js的模拟版本。另一方面,存根数学。随机应该可以工作,因为这对所有模块都是全局的。

最新更新