我希望能够正确测试我的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;
B类
class B {
constructor() {}
doOther() {
return new Promise( (resolve, reject) => {
// various things that will resolve or reject
});
}
module.exports = new B();
索引
const A = require('A');
const b = require('b');
const a = new A(b);
a.doSomething(123)
.then(() => {
// things
});
由于我试图进行依赖注入而不是在课程顶部需要,我不确定如何去模拟B类及其测试A类的功能
答案 0 :(得分:4)
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...
});
});
});
});
然而,这并不是我所推荐的。
我通常会尽量避免教条式的建议,但这只是为数不多的例外之一。如果你正在进行单元测试,TDD或BDD,你通常应该避免单身。它们与这些实践并不完美,因为它们在测试后进行清理要困难得多。在上面的示例中,它非常简单,但随着B
类添加了越来越多的功能,清理变得越来越繁琐,容易出错。
那你做什么呢?让B
模块导出B
类。如果您想保留DI模式并避免在B
模块中使用A
模块,那么每次进行{{}时,您只需要创建一个新的B
实例{1}}实例。
遵循此建议,您的测试可能如下所示:
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...
});
});
});
});
实例,因此不再需要恢复存根B
方法。
Sinon还有一个称为createStubInstance的简洁实用程序函数,它允许您在测试期间完全避免调用doSomething
构造函数。它基本上只是为任何原型方法创建一个带有存根的空对象:
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...
});
});
});
});
构造函数永远不应该用来包装promises。这样做是多余和令人困惑的,并且违背了承诺的目的,即使异步代码更容易编写。
Promise.prototype.then方法内置了有用的行为,因此您永远不必执行此冗余包装。调用Promise
总是会返回一个承诺(我将在下文中称之为'链式承诺'),其状态将取决于处理程序:
then
处理程序将导致链式承诺以该值解析。then
处理程序将导致链式承诺拒绝抛出的值。then
处理程序将使链接的promise与该返回的promise的状态相匹配。因此,如果它以某个值结算或拒绝,则链式承诺将以相同的值解析或拒绝。所以你的then
课程可以大大简化:
A
答案 1 :(得分:1)
我认为您正在搜索proxyquire库。
为了证明这一点,我编辑了一些你的文件,直接在中包含b(我这样做是因为你的单身new B
),但你可以保留你的代码,它是用这个更容易理解代理。
class B {
constructor() {}
doOther(number) {
return new Promise(resolve => resolve(`B${number}`));
}
}
module.exports = new B();
const b = require('./b');
class A {
testThis(number) {
return b.doOther(number)
.then(result => `res for ${number} is ${result}`);
}
}
module.exports = A;
我现在想通过模仿b的行为来测试a.js
。在这里你可以这样做:
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__');
});
});
使用proxyquire,您可以轻松地模拟依赖项的依赖项,并对模拟的lib执行期望。 sinon用于直接间谍/存根对象,你必须经常使用它们。
答案 2 :(得分:1)
看起来非常简单,因为sinon
通过用行为替换其中一个方法来模仿对象(如here所述):
(我将resolve()
- s添加到您的函数中的两个承诺中以便能够进行测试)
const sinon = require('sinon');
const A = require('./A');
const b = require('./b');
describe('Test A using B', () => {
it('should verify B.doOther', async () => {
const mockB = sinon.mock(b);
mockB.expects("doOther").once().returns(Promise.resolve());
const a = new A(b);
return a.doSomething(123)
.then(() => {
// things
mockB.verify();
});
});
});
如果我误解了您想要测试的内容或其他细节,请告诉我......