sinon stub命名空间函数

时间:2017-06-26 15:11:31

标签: node.js unit-testing namespaces metaprogramming sinon

我在使用sinon存根时遇到了一些问题,这可能源于我如何在我想要存根的模块上实现命名空间。直接在原型上定义的方法是我所期望的。

...my module.js 
const Constructor = require('./constructor') //...just exports a singleton

/* Need to namespace some of my functions and retain the `this` context */

Object.defineProperty(Constructor.prototype, 'es', {
  get: function() {
    return {
      method: require('./implementations/doesSomething.js').bind(this)
    }
  }
});

module.exports = Constructor;




/* ...testFile.js */
const Constructor = require('./constructor');
const instance = new Constructor();
const sinon = require('sinon');

sinon.stub(instance.es, 'method', function() {
   return 'hijacked original method'
});

1 个答案:

答案 0 :(得分:1)

正如前面提到的on the Sinon issue tracker,问题在于使用普通Object.defineProperty(obj, 'prop')调用除了使用赋值(obj['prop'] = ...)明确创建它之外还有其他功能。

通常,如果您尝试在没有Object.defineProperty的情况下定义属性,那么它将是可存根的,但使用defineProperty(不创建特殊配置)将无法存根该属性。原因很简单,default values for writeable and configurablefalse!你不能delete他们或改变他们。如果你不能那样做,那么诗乃不会帮助你。因此,通常,您需要在属性定义中添加writeable: true, configurable: true

现在我还有一件事忘了回答: 您并没有尝试在Constructor.prototype.es.method上包装函数 - 您尝试包装的是由es属性上的getter返回的对象上的函数。这将从不工作。 为什么?只是因为返回的对象永远不会相同。您每次都在method周围创建一个新对象。如果您确实需要替换/存根method属性,则实际上需要替换整个Constructor.prototype.es属性。如果你需要这个命名空间,你可以大大简化这个,并且还可以启用存根,如下所示:

Constructor.prototype.es = {};

Object.defineProperty(Constructor.prototype.es, 'method', {
  get: function() {
    return someFunction.bind(this);
  },
  writeable: true,
  configurable:true
}

一个扩展的,完全有效的例子(Gist for download):

// constructor.js
const someFunction = function(){
    return this.value;
}

function Constructor(){ };
Constructor.prototype.es = { value : 100 };

Object.defineProperty(Constructor.prototype.es, 'method', {
  get: function() {
    return someFunction.bind(this);
  },
  writeable: true,
  configurable:true
});

// test.js
const instance = new Constructor();

console.log(instance.es.method()) // => 100

// using this won't work:
// sinon.stub(instance.__proto__.es, 'method').returns(42);
// because the getter is returning a _new_ function each time
// therefore you need to attack the actual getter function:

const stub = sinon.stub(instance.__proto__.es, 'method').value(()=>42);
console.log(instance.es.method()) // => 42
stub.get(()=>()=>84);
console.log(instance.es.method()) // => 84
stub.restore();
console.log(instance.es.method()) // => 100

// the above is working on the prototype, can't we do this on the instance?
// yes, we can, but remember that the `es` object is shared, so we
// can avoid modifying it by shadowing it further down the prototype
instance.es = { method: sinon.stub().returns(256) };
console.log(instance.es.method()) // => 256
delete instance.es
console.log(instance.es.method()) // => 100
<script src="https://unpkg.com/sinon@2.3.5/pkg/sinon.js"></script>