Jar*_*ede 3 javascript bdd mocha.js node.js sinon
我希望能够正确测试我的ES6类,它的构造函数需要另一个类,所有这些看起来像这样:
A级
class A {
constructor(b) {
this.b = b;
}
doSomething(id) {
return new Promise( (resolve, reject) => {
this.b.doOther()
.then( () => {
// various things that will resolve or reject
});
});
}
}
module.exports = A;
Run Code Online (Sandbox Code Playgroud)
B级
class B {
constructor() {}
doOther() {
return new Promise( (resolve, reject) => {
// various things that will resolve or reject
});
}
module.exports = new B();
Run Code Online (Sandbox Code Playgroud)
指数
const A = require('A');
const b = require('b');
const a = new A(b);
a.doSomething(123)
.then(() => {
// things
});
Run Code Online (Sandbox Code Playgroud)
由于我试图进行依赖注入而不是在类的顶部进行需求注入,因此我不确定如何模拟类B及其用于测试类A的功能。
我认为您正在寻找proxyquire库。
为了演示这一点,我对您的文件进行了一些编辑,以直接将 b 包含在 a 中(我这样做是因为您的 singleton new B),但是您可以保留您的代码,这样更容易理解 proxyquire 。
class B {
constructor() {}
doOther(number) {
return new Promise(resolve => resolve(`B${number}`));
}
}
module.exports = new B();
Run Code Online (Sandbox Code Playgroud)
const b = require('./b');
class A {
testThis(number) {
return b.doOther(number)
.then(result => `res for ${number} is ${result}`);
}
}
module.exports = A;
Run Code Online (Sandbox Code Playgroud)
我现在想a.js通过模拟 b 的行为来进行测试。在这里你可以这样做:
const proxyquire = require('proxyquire');
const expect = require('chai').expect;
describe('Test A', () => {
it('should resolve with B', async() => { // Use `chai-as-promised` for Promise like tests
const bMock = {
doOther: (num) => {
expect(num).to.equal(123);
return Promise.resolve('__PROXYQUIRE_HEY__')
}
};
const A = proxyquire('./a', { './b': bMock });
const instance = new A();
const output = await instance.testThis(123);
expect(output).to.equal('res for 123 is __PROXYQUIRE_HEY__');
});
});
Run Code Online (Sandbox Code Playgroud)
使用 proxyquire 您可以轻松模拟依赖项的依赖项并对模拟的库进行期望。sinon用于直接监视/存根对象,通常必须同时使用它们。
Sinon允许您轻松地对对象的各个实例方法进行存根。当然,由于b是单例,因此您需要在每次测试后将其回滚,以及可能对进行的其他任何更改b。如果您不这样做,则呼叫计数和其他状态将从一项测试泄漏到另一项测试中。如果这种全局状态处理不当,则您的套件可能会变成依赖其他测试的测试缠结。
重新排序一些测试?以前没有发生过的某些故障。添加,更改或删除测试?许多其他测试现在失败。尝试运行单个测试还是测试子集?他们现在可能会失败。或更糟糕的是,当您编写或编辑它们时,它们会孤立地通过,但是当整个套件运行时它们会失败。
相信我,这很糟糕。
因此,遵循此建议,您的测试可能类似于以下内容:
const sinon = require('sinon');
const { expect } = require('chai');
const A = require('./a');
const b = require('./b');
describe('A', function() {
describe('#doSomething', function() {
beforeEach(function() {
sinon.stub(b, 'doSomething').resolves();
});
afterEach(function() {
b.doSomething.restore();
});
it('does something', function() {
let a = new A(b);
return a.doSomething()
.then(() => {
sinon.assert.calledOnce(b.doSomething);
// Whatever other assertions you might want...
});
});
});
});
Run Code Online (Sandbox Code Playgroud)
但是,这并不是我所建议的。
我通常会尽量避免教条式的建议,但这将是少数例外之一。如果要进行单元测试,TDD或BDD,则通常应避免单例。它们与这些实践不能很好地融合在一起,因为它们使测试后的清理更加困难。在上面的示例中,这很简单,但是随着B类中添加了越来越多的功能,清理工作变得越来越繁重并且容易出错。
那你该怎么办呢?让您的B模块导出B类。如果要保留自己的DI模式并避免在B模块中使用该A模块,则B每次创建一个实例时只需创建一个新A实例。
遵循此建议,您的测试可能如下所示:
const sinon = require('sinon');
const { expect } = require('chai');
const A = require('./a');
const B = require('./b');
describe('A', function() {
describe('#doSomething', function() {
it('does something', function() {
let b = new B();
let a = new A(b);
sinon.stub(b, 'doSomething').resolves();
return a.doSomething()
.then(() => {
sinon.assert.calledOnce(b.doSomething);
// Whatever other assertions you might want...
});
});
});
});
Run Code Online (Sandbox Code Playgroud)
您会注意到,由于B每次都会重新创建实例,因此不再需要还原存根doSomething方法。
Sinon还具有一个名为createStubInstance的简洁实用程序函数,该函数可避免B在测试期间完全调用构造函数。基本上,它只是为任何原型方法创建一个带有存根的空对象:
const sinon = require('sinon');
const { expect } = require('chai');
const A = require('./a');
const B = require('./b');
describe('A', function() {
describe('#doSomething', function() {
it('does something', function() {
let b = sinon.createStubInstance(B);
let a = new A(b);
b.doSomething.resolves();
return a.doSomething()
.then(() => {
sinon.assert.calledOnce(b.doSomething);
// Whatever other assertions you might want...
});
});
});
});
Run Code Online (Sandbox Code Playgroud)
最后,与问题没有直接关系的最后建议- Promise构造函数永远不要用于包装promise。这样做是多余和混乱的,并且违反了使异步代码更易于编写的promise的目的。
该Promise.prototype.then方法附带了一些有用的行为内置的,所以你永远不应该执行此多余的包装。调用then总是返回一个promise(此后称为“链接的promise”),其状态将取决于处理程序:
then返回非承诺值处理将导致链的承诺,以解决该值。then引发的处理程序将导致链式承诺拒绝使用引发的值。then返回一个承诺处理将导致链的承诺,以匹配返回承诺的状态。因此,如果它用一个值解决或拒绝,则链接的承诺将以相同的值解决或拒绝。因此,您的A班级可以大大简化,如下所示:
class A {
constructor(b) {
this.b = b;
}
doSomething(id) {
return this.b.doOther()
.then(() =>{
// various things that will return or throw
});
}
}
module.exports = A;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3335 次 |
| 最近记录: |