Search code examples
reactjsunit-testingtddreact-testing-library

Should I be testing if function was called in React?


So I'm trying to get into TDD with React. Everywhere I read about testing in general, it's always emphasised that we only want to test behaviour, not implementation details. Sounds good, but then when going through some examples, I find stuff like this:

expect(onSubmit).toBeCalledTimes(1)

Is this not an implementation detail? Should I really care if a function called "onSubmit" is being called? What if tomorrow it changes to "onFormSubmit"? Or what if the function is empty? The test would still pass...

Shouldn't we test instead if whatever onSubmit is supposed to do, is done? Like check if a DOM element changes or something...

I swear, it's being a real trip trying to grasp the art of writing tests (in general, not just in React).


Solution

  • If you want to avoid testing every implementation detail and only test the things a user cares about, you would need to do an end-to-end test with a tool like Cypress against a real backend and real database. The realism of these tests have a lot of value. But there are downsides as well: they can be slow and less reliable, so it isn't realistic to fully cover all code paths of a large application with them.

    As soon as you drop down to component tests, you are already testing some things the user doesn't care about: if you pass props to the component or mock out a backend, the user doesn't care about those props or backend API. But it's a worthwhile tradeoff because it allows you to have faster tests that cover more edge cases.

    In component tests I would recommend testing the component's inputs and outputs. If an onSubmit function is defined inside your component's render function, then I would not run an assertion against it; that's definitely an implementationn detail. Instead, I would test the result of that function running. But if onSubmit is passed in as a prop of the function, then I would pass in a mock function and check that it was called, yes. If it's a function defined in another module, things get fuzzier: you can decide whether you want to mock that module or test against the real implementation of that function.

    There are always tradeoffs in testing; the above are some of the things you can consider when deciding how to test a specific case.