【问题标题】:How can I assert a function has `await`ed an async function using should.js如何使用 should.js 断言函数已“等待”异步函数
【发布时间】:2019-04-30 22:48:46
【问题描述】:

我有一个异步函数f,它调用另一个异步函数g。为了测试f 是否调用g,我使用sinon 对g 进行存根,并断言它是使用should.js 调用的。

'use strict';

require('should-sinon');
const sinon = require('sinon');

class X {
    async f(n) {
        await this.g(n);
        // this.g(n); // I forget to insert `await`!
    }
    async g(n) {
        // Do something asynchronously
    }
}

describe('f', () => {
    it('should call g', async () => {
        const x = new X();
        sinon.stub(x, 'g').resolves();
        await x.f(10);
        x.g.should.be.calledWith(10);
    });
});

但即使我在f 中调用g 时忘记使用await,此测试也通过了。

捕获此错误的方法之一是让存根返回一个虚拟承诺并检查其 then 是否被调用。

it('should call g', async () => {
    const x = new X();
    const dummyPromise = {
        then: sinon.stub().yields()
    };
    sinon.stub(x, 'g').returns(dummyPromise);
    await x.f(10);
    x.g.should.be.calledWith(10);
    dummyPromise.then.should.be.called();
});

但这有点麻烦。有什么方便的方法吗?

【问题讨论】:

  • should-sinon 是否拉入sinon?大概你想require('sinon') 之前 require('should-sinon')?

标签: javascript node.js sinon should.js


【解决方案1】:

f 的示例显示了有缺陷的代码设计,如果您在没有 async/await 语法的情况下编写相同的函数,这将变得更加明显:

f(n) { return g(n).then(()=>{}); }

这实现了相同的行为 - g 是否已解决变得难以判断(假设您不知道 f 是否返回了 g 的承诺,这与不知道 f 是否等待 @ 相同987654328@)。如果fg 的结果不感兴趣,它应该只是简单地返回它,而不是隐藏它。然后您可以简单地测试结果。

如果您的观点是 f 可能必须依次触发多个 async 调用 awaiting 几个 g_1g_2... 来解决,那么您可以通过断言来构建测试链g_n+1 的存根,g_n 的虚拟承诺已得到解决。一般来说,您测试虚拟承诺的状态的方法很好。

【讨论】:

  • 我不一定同意这是有缺陷的代码设计。可能是他不希望f 返回的承诺暴露g 解析的值。您使用then 语法的示例正是没有async/await 的人可能会如何做到这一点。
  • 我明白你的意思,是的,在某些情况下你可能不想返回 g 返回的内容。你可能想调用几个g_1g_2,...正如我提到的,或者只是一个但可能想阻止访问引用 - 但是即使在这些情况下,我仍然认为返回一些特定的映射更干净,例如实例 ID。完全隐藏代理函数的结果,同时想要保证代理函数运行的可测试性似乎有点自相矛盾。
  • 这很合理。实际上,这一切都归结为g 实际做了什么,以及通过f 公开哪些操作是可以接受的。当然,测试会更干净,而不必像我在回答中那样手动跟踪 g 的完成状态。
【解决方案2】:

最好不要存根then,最好不要存根g,以便在下一次事件循环迭代中设置一些布尔值。然后,您可以在调用 f 后检查此布尔值,以确保 f 等待它:

it('should call g', async () => {
    const x = new X();
    let gFinished = false;
    sinon.stub(x, 'g').callsFake(() => {
        return new Promise((resolve) => {
            setImmediate(() => {
                gFinished = true;
                resolve();
            });
        });
    });
    await x.f(10);
    x.g.should.be.calledWith(10);
    gFinished.should.be.true();
});

编辑:当然,这不是一个完美保证,因为您可以让f 等待任何至少等待g 解决的承诺。像这样:

async f(n) {
    this.g(n);
    await new Promise((resolve) => {
        setImmediate(() => {
            resolve();
        });
    });
}

这将导致我编写的测试通过,即使它仍然不正确。所以真的归结为你对测试的严格程度。您是否希望字面上不可能出现误报?或者,如果一些明显的诡计可能会把它扔掉,这可以吗?

在大多数情况下,我发现后者没问题,但这实际上取决于您和/或您的团队。

【讨论】:

    猜你喜欢
    • 2018-09-29
    • 1970-01-01
    • 1970-01-01
    • 2017-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    相关资源
    最近更新 更多