3

I'm using Karma with Mocha, Chai and Sinon to test code in a project using this boilerplate. The Subject Under Test uses the Speech Synthesis API.

I start by establishing window.speechSynthesis.getVoices in a beforeEach method

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

Then I have two test cases, in each one, I want to test what happens when a different set of voices is returned. To accomplish this I'm using Sinon stubs

First test case

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

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

Second test case

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

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

The problem is, when the SUT calls window.speechSynthesis.getVoices during the second test case, it's getting the results from the first stub. It's as if the second stub is doing nothing...

If I comment out the first test case, the second test case succeeds, but if I leave them both in, the second one fails because the wrong set of voices are being returned.

Any idea how to get the second stub to work as expected?

quickshiftin
  • 66,362
  • 10
  • 68
  • 89

2 Answers2

4

Your stub is not destroyed between tests. You need to restore the default function after a test and create your stub only once in 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
  • 5,127
  • 1
  • 37
  • 64
  • I added the `afterEach`, but the results are the same. The stub creation is now only in `beforeEach` as you have it here. – quickshiftin Sep 21 '17 at 14:52
  • Tried it, same result. – quickshiftin Sep 21 '17 at 14:55
  • So it looks like in the second test case, after I call `getVoicesStub.callsFake()`, it's still returning `null`. I've also tried `global.window.speechSynthesis.getVoices.restore()` with the same result. Agree with you though, your solution looks like it *should* work. I'll keep experimenting. – quickshiftin Sep 21 '17 at 15:43
  • Got it working!! Will put the details in a separate answer just so it's out there. – quickshiftin Sep 21 '17 at 16:09
0

First off, big-ups to @Troopers. Just adding this answer to share the final solution and details I noticed along the way.


The real trick was adding a test-suite-level variable let getVoicesStub, then defining an afterEach method to restore the original function

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

A subtle caveat to @Troopers suggestion about defining the stub in a before method -

If the stub is defined outside of the test cases, I have to use a beforeEach, if the stub is defined within the test cases, I have to use a before method.

In both cases, the afterEach is critical! I've settled on the beforeEach solution as the stub is only defined in one place so there's slightly less code.

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 ..
    });
});
quickshiftin
  • 66,362
  • 10
  • 68
  • 89