gpt4 book ai didi

javascript - 如何使用 sinon 正确模拟 ES6 类

转载 作者:搜寻专家 更新时间:2023-10-31 23:31:12 25 4
gpt4 key购买 nike

我希望能够正确测试我的 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
});

由于我尝试进行依赖注入(inject)而不是在类的顶部要求,我不确定如何模拟类 B 及其用于测试类 A 的函数。

最佳答案

Sinon 允许您轻松地 stub 对象的各个实例方法。当然,由于 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...
});
});
});
});

但是,这并不是我所推荐的。

我通常尽量避免教条式的建议,但这是少数异常(exception)之一。如果您正在进行单元测试、TDD 或 BDD,通常应该避免使用单例。它们不能与这些实践很好地结合,因为它们使测试后的清理变得更加困难。在上面的示例中,它非常简单,但是随着 B 类添加了越来越多的功能,清理工作变得越来越繁重并且容易出错。

那么你会怎么做呢?让您的 B 模块导出 B 类。如果你想保留你的 DI 模式并避免在 A 模块中需要 B 模块,你只需要创建一个新的 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...
});
});
});
});

您会注意到,因为每次都会重新创建 B 实例,所以不再需要恢复 stub 的 doSomething 方法。

Sinon 还有一个简洁的实用函数,叫做 createStubInstance这使您可以避免在测试期间完全调用 B 构造函数。它基本上只是为任何原型(prototype)方法创建一个带有 stub 的空对象:

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...
});
});
});
});

最后,与问题没有直接关系的最后一点建议 -- Promise 构造函数不应该用于包装 promises。这样做是多余且令人困惑的,并且违背了使异步代码更易于编写的 promise 的目的。

Promise.prototype.then方法带有内置的有用行为,因此您永远不必执行这种冗余包装。调用 then 总是返回一个 promise(我将在下文中称之为“链式 promise”),其状态将取决于处理程序:

  • 返回非 promise 值的 then 处理程序将导致链式 promise 以该值解析。
  • 抛出的 then 处理程序将导致链式 promise 拒绝抛出的值。
  • 返回 promise 的 then 处理程序将使链接的 promise 与返回的 promise 的状态相匹配。因此,如果它使用一个值解决或拒绝,则链式 promise 将使用相同的值解决或拒绝。

所以你的 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;

关于javascript - 如何使用 sinon 正确模拟 ES6 类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50389981/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com