What it means to not test implementation details in React
@RozenMD
Are you unsure of what exactly you're trying to achieve when writing tests? Do you write tests based on other existing tests in your codebase? Perhaps you've quickly glanced at the React Testing Library documentation to try figure out "the right way" of testing.
At some point, you need to step back and think:
What does 'the right way' even mean when testing React?
What are we trying to achieve here?
If you've heard of React Testing Library, chances are you've heard the almost-slogan "test functionality, not implementation details".
Not testing implementation details requires a pretty large mindset shift if you're used to testing with Enzyme, as it provides several utilities explicitly for that purpose, such as find()
, instance()
and state()
.
For me, it helps to think from the perspective of a malicious user. Think about a sign-up form. I might try submit the form with no data to see what happens, maybe try submit the form without a password, or supply an invalid email address.
Just like that, you've got tests to write with no knowledge of implementation details:
Test 1 - no data
- Render the form
- Find the submit button, and click it
- Assert that the error message appears
Test 2 - no password
- Render the form
- Find the email field, and enter a random valid email address
- Find the submit button, and click it
- Assert that the error message appears
Test 3 - invalid email address
- Render the form
- Find the username field, and enter an invalid email address
- Find the submit button, and click it
- Assert that the error message appears
Side note: Once you've got a few tests like this, you can look at refactoring them to avoid duplicating code in your tests.
React Testing Library helps quite a bit with utility functions that let you easily write tests like this, but Enzyme will let you do the same thing too.
The important thing is to test functionality, because it'll let you avoid writing brittle tests that rely too much on knowing which functions to call, and hopefully means you'll be rewriting your tests less often as your code gets refactored.
Conclusion
I find the easiest way to avoid testing implementation details is to pretend I'm not a developer, and try to perform a specific task in my application just like a user would.
For example, typing into a sign-up form, and trying to submit the form.
The tests I write would then seek to automate the clicking and typing the user would do, rather than calling find(MyComponent)
and calling functions directly.