Search code examples
javascriptreactjsenzymesinonsinon-chai

Sinon stub only records react calls if it has been run before


Here is my code I'm testing:

const CardWarsComponent = props => {
    const onSubmitCardMove = () => {
        props.floopThePig()
    }
    return <Button id="FloopButtonId" onClick={onSubmitCardMove}>FLOOP IT</Button>
};

When I test I do this:

const floopStub = sinon.stub();
const wrapper = shallow(<CardWarsComponent floopThePig={floopStub}></CardWarsComponent>)
const button = wrapper.find('#FloopButtonId').dive();
const onClick = button.prop('onClick');
onClick();
wrapper.update();
expect(floopStub).to.have.been.calledOnce();

I find that I get this response:

TypeError: (0, _chai.expect)(...).to.have.been.calledOnce is not a function

BUT, if I call it once already:

const floopStub = sinon.stub();
floopStub()
const wrapper = shallow(<CardWarsComponent floopThePig={floopMock}></CardWarsComponent>)

Then I get:

 expected floopThePig to have been called exactly once, but was called twice
floopThePig at Context.floopThePig
floopThePig at floopThePig

So why can I not get it called once, but after calling it once...I get the second (actual test) call?


Solution

  • The initial error message you got is exactly correct - looking at the Sinon docs, .calledOnce is a not a function, it's a javascript get that runs code. In general, Chai expectations only seem to be functions if they need parameters.

    I find this a bit annoying because get overloading isn't very nice and breaks our ability to reason about the code, and it sets off a bunch of lint errors, but hey ho.

    Changing your expect to

    expect(floopStub).to.have.been.calledOnce;
    

    fixes the test (at least on my machine!)

    The reason the code seems to "work" in your second example is because the test resolves the calledOnce get operation, which checks the condition and throws a test failure assertion, so there's never a chance to throw the original error by trying to call the get result as if it were a function.

    Incidentally, it's easier to trigger the click event with enzyme's built-in .simulate() call. This code works fine:

    const floopStub = sinon.stub();
    const wrapper = shallow(<CardWarsComponent floopThePig={floopMock}></CardWarsComponent>);
    wrapper.find('#FloopButtonId').dive().simulate('click');
    expect(floopStub).to.have.been.calledOnce;
    

    More info on testing React events with sinon/enzyme can be found here: https://www.leighhalliday.com/testing-react-jest-enzyme-sinon