如何使用 Sinon JS 测试异步函数?

How to test async function with Sinon JS?

下面是我想要实现的最小示例:我想测试如果异步函数 fn2() 已解析(调用 fn1 时),将调用 fn3()。但不知何故,我没能用 sinon 的存根语法做到这一点。我想知道我误会了什么。

// System Under Test

export function fn1() {
    fn2().then(() => {
        fn3();
    });
}

export async function fn2() {
    return new Promise((resolve, reject) => {
        // expensive work
        resolve();
    });
}

export function fn3() {
    console.log("fn3");
}

// Test

import * as Page from "xxx";

it("test async", () => {
    // stub this async to isolate the SUT
    sinon.stub(Page, "fn2").resolves();
    const stub = sinon.stub(Page, "fn3");

    Page.fn1();

    sinon.assert.calledOnce(stub);
});


/*

    AssertError: expected fn3 to be called once but was called 0 times

      276 |         Page.fn1();
      277 |
    > 278 |         sinon.assert.calledOnce(stub);
          |                      ^
      279 |     });
      280 | });

      at Object.fail (node_modules/sinon/lib/sinon/assert.js:107:21)
      at failAssertion (node_modules/sinon/lib/sinon/assert.js:66:16)
      at Object.calledOnce (node_modules/sinon/lib/sinon/assert.js:92:13)
      at Object.<anonymous> (src/test.test.tsx:278:22)
*/

正如@jonrsharpe 所建议的那样,您的 sinon.assert.calledOnce(stub); 将在 Page.fn1 内的 fn2 承诺解决之前被调用(即 .then(() => { fn3(); }) 被调用),基于 JavaScript“单线程,事件循环”的性质。这里有一篇文章供参考:https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke.

您可以尝试将代码更改为:

    ...

    return Page.fn1().then(() => {
      sinon.assert.calledOnce(stub);
    });
    
    ...

it("test async", async () => {
    // stub this async to isolate the SUT
    sinon.stub(Page, "fn2").resolves();
    const stub = sinon.stub(Page, "fn3");

    await Page.fn1();

    sinon.assert.calledOnce(stub);
});

以便在 Page.fn1() 解决后调用 sinon.assert.calledOnce(stub);