Skip to content Accessibility

Assert Zero Arguments

Always favour explicitness over in-explicitness when testing function calls.

Published

Categories

When testing a function call it is important to check the expected arguments are passed, including functions that have zero arguments.

In the following React component we want to test that clicking the "Exit survey" button calls the function passed to its onExit prop:

Programming language (abbreviated): jsx

const Survey = ({ onExit }) => (
<button type="button" data-test="exit-survey" onClick={onExit}>
Exit survey
</button>
);

This interaction can be tested using toBeCalled:

Programming language (abbreviated): js

it('To be called', () => {
const onExitMock = jest.fn().mockName('onExitMock');
const wrapper = shallow(<Survey onExit={onExitMock} />);

wrapper.find("[data-test='exit-survey']").simulate('click');

expect(onExitMock).toBeCalled();
});

/**
Produces:

PASS src/Survey.spec.js
✅ To be called
*/

After some user testing the UX team find a higher number of participants exit the survey than hoped. Perhaps personalizing the exit message will encourage more people to reconsider.

We update our component to capture the participants name, and subsequently pass it to the onExit prop:

Programming language (abbreviated): js

const Survey = ({ onExit }) => {
const [participantName, setParticipantName] = useState('');

return (
<>
<input
value={participantName}
data-test="participant-name"
onChange={({ target }) => setParticipantName(target.value)}
/>
<button type="button" data-test="exit-survey" onClick={() => onExit(participantName)}>
Exit survey
</button>
</>
);
};

/**
Produces:

PASS src/Survey.spec.js
✅ To be called
*/

Interestingly, but not unexpectedly our test still passes. Technically the assertion is still correct because the onExit prop is still called.

However the test lacks the explicitness required to catch a change in contract between the function and its caller. It lacks awareness of what arguments (if any) the function is called with. What if the parent component of Survey still assumes onExit receives zero arguments?

This is where asserting zero arguments can help. If our original test used toBeCalledWith instead of toBeCalled this change in arity would have been caught:

Programming language (abbreviated): js

it('To be called with', () => {
const onExitMock = jest.fn().mockName('onExitMock');
const wrapper = shallow(<Survey onExit={onExitMock} />);

wrapper.find("[data-test='exit-survey']").simulate('click');

expect(onExitMock).toBeCalledWith();
});

/**
Produces:

FAIL src/Survey.spec.js
❌ To be called with

● To be called with

expect(onExitMock).toBeCalledWith(...expected)

Expected: called with 0 arguments
Received: ""
*/

Now with the test in a failing state it can be amended it to reflect the new functionality. Not only does this improve the accuracy of the test – it also reduces the likeness of a parent component assuming onExit has zero arguments.

Programming language (abbreviated): js

it('To be called with the participant name', () => {
const onExitMock = jest.fn().mockName('onExitMock');
const wrapper = shallow(<Survey onExit={onExitMock} />);
const givenParticipantName = 'John';

wrapper
.find("[data-test='participant-name']")
.simulate('change', { target: { value: givenParticipantName } });
wrapper.find("[data-test='exit-survey']").simulate('click');

expect(onExitMock).toBeCalledWith(givenParticipantName);
});

/**
Produces:

PASS src/Survey.spec.js
✅ To be called with the participant name
*/

Always favour explicitness over in-explicitness when testing function calls!

About the author

I'm Callum, a Front-end Engineer at Nutmeg. Previously I wrote code for KAYAK, American Express, and Dell. Out of hours I publish blog posts (like this one) and tweet cherry-picks.

Feel free to follow or message me at @_callumhart on Twitter.