针对全局 window 的多个 Sinon 存根未按预期工作

Multiple Sinon stubs against global window not working as expected

我将 Karma 与 Mocha、Chai 和 Sinon 结合使用来测试使用 this boilerplate. The Subject Under Test uses the Speech Synthesis API 的项目中的代码。

我首先在 beforeEach 方法中建立 window.speechSynthesis.getVoices

beforeEach(() => {
    global.window.speechSynthesis = {
        getVoices: () => (null),
    };
});

然后我有两个测试用例,在每个测试用例中,我想测试当返回一组不同的声音时会发生什么。为此,我使用 Sinon stubs

第一个测试用例

it('supports speech and locale', () => {
    const getVoicesStub = sinon.stub(
        global.window.speechSynthesis,
        'getVoices');

    getVoicesStub.callsFake(() => (
        [{lang: 'en_US'}]
    ));

第二个测试用例

it('will choose best matching locale', () => {
    const getVoicesStub = sinon.stub(
        global.window.speechSynthesis,
        'getVoices');

    getVoicesStub.callsFake(() => (
        [{lang: 'es_MX'}, {lang: 'es_US'}]
    ));

问题是,当 SUT 在第二个测试用例中调用 window.speechSynthesis.getVoices 时,它从第一个存根中获取结果。好像第二个存根什么也没做...

如果我注释掉第一个测试用例,第二个测试用例会成功,但如果我把它们都留在里面,第二个测试用例会失败,因为返回了错误的声音集。

知道如何让第二个存根按预期工作吗?

你的存根在测试之间没有被破坏。您需要在测试后恢复默认功能,并在 before

中仅创建一次存根
describe("Test suite", () => {

    let getVoicesStub;

    before(() => {
        // executes before suite starts
        global.window.speechSynthesis = {
            getVoices: () => (null),
        };

        getVoicesStub = sinon.stub(global.window.speechSynthesis, 'getVoices');
    });

    afterEach(() => {
        // executes after each test
        getVoicesStub.restore();
    });

    it('supports speech and locale', () => {
        getVoicesStub.callsFake(() => ([{lang: 'en_US'}]));
    });

    it('will choose best matching locale', () => {
        getVoicesStub.callsFake(() => ([{lang: 'es_MX'}, {lang: 'es_US'}]));
    });
});

首先,感谢@Troopers。只需添加此答案即可分享我一路上注意到的最终解决方案和细节。


真正的诀窍是添加一个测试套件级变量 let getVoicesStub,然后为 restore 原始函数

定义一个 afterEach 方法
afterEach(() => {
    getVoicesStub.restore();
});

@Troopers 关于在 before 方法中定义存根的建议的微妙警告 -

如果存根是在测试用例之外定义的,我必须使用beforeEach,如果存根是在测试用例内定义的,我必须使用before方法。

在这两种情况下,afterEach 都很关键!我选择了 beforeEach 解决方案,因为存根仅在一个地方定义,因此代码略少。

describe('Browser Speech', () => {
    let getVoicesStub;

    beforeEach(() => {
        global.window.speechSynthesis = {
            getVoices: () => (null),
        };

        getVoicesStub = sinon.stub(
            global.window.speechSynthesis,
            'getVoices');
    });

    afterEach(() => {
        getVoicesStub.restore();
    });

    it('supports speech and locale', () => {
        getVoicesStub.callsFake(() => (
            [{lang: 'en_US'}]
        ));

        // test case code ..
    });

    it('will choose best matching locale', () => {
        getVoicesStub.callsFake(() => (
            [{lang: 'es_MX'}, {lang: 'es_US'}]
        ));


        // test case code ..
    });
});