The importance of testing is well documented and there are many resources out there describing the benefits of maintaining a good and balanced test coverage for your code base.
Gladly writing tests has become a standard in our industry, but sometimes the need (or requirement) to write them obscures one’s vision on what exactly should be tested.
From time to time I get asked to help with a certain test, mainly on mocking practices (I actually wrote a Jest Mocking Cheatsheet not too long ago, just to keep a reference for myself) and I find that after being presented with the immediate issue, the first question I usually ask is:
“What are you trying to test?”
This question rises almost every time and this question has the potential to untangle the problem and result in a much simpler yet efficient solution. I thought it would be worth sharing how it does that with you -
Developers, including yours truly, are having a hard time focusing on what needs to be tested since their focus is on the entire feature, and how the user interacts with it.
This focus makes it hard to pluck out the very thing you wish to test, and you find yourself testing the entire jungle just because you wanted to check if a certain tree has a certain fruit.
Usually the answer to the question “what are you trying to test?” will describe a set of conditions which result in a state you would like to test, an example for that might be:
“I would like to test that when the user clicks a button on my component, a modal opens with a confirmation message, and when the user confirms, a certain text appears on the component”
So… what type of test is that?
The flow described above goes through different units - the component, the modal and then back to the component. Clearly we’re not dealing with a “unit test” here. This appears to be more of an integration test where different services/components are integrated to fulfill a user interaction flow.
Now, before jumping into how to “mimic” this flow in our test we need to ask whether that’s our intention - writing an integration test.
In many cases the answer is “no, I just want to make sure that when the application state is so-and-so my component displays a certain text”.
It doesn't really matter what set that state.
I believe that the art of writing a good unit-test is peeling off redundant setups to get the test as focused as possible on what we want to check.
If the only reason for this test is “making sure that when the application state is so-and-so my component displays a certain text” what the tester needs to focus on is creating that state artificially and then checking the component.
Obviously there is room for a complimentary integration as well - now that you know that your component acts as expected to state changes, let’s change the state from another component or service and see if all works as expected.
It’s not “either this or that”
Unit tests and integration tests testing the same area can and should live side by side. An integration test, as best as it might be, is no replacement for a good comprehensive unit-test, in my eyes, and vice versa. Those memes where you see the caption “unit tests passing, no integration tests'' are funny but tell a true story - you should have both.
So you decided that the type of test you need is a test which does have several units integrated under it. What should it be - integration or E2E test? is there a difference?
I see integration tests more suitable for checking communication between different services, API to an API communication without any external user intervention. Let’s call the “pure integration tests” for now.
On the other hand, any test which involves user interaction as the example described above is worth an E2E test. I think that although modern testing libraries give us the tools to test these sorts of interaction flows, a real E2E test which runs on a real browser with the real full application set up and ready is much more reliable than mimicking the entire application runtime.
Since it is objectively harder to write and maintain Integration or E2E tests, developers tend to write the equivalents as unit tests. What I mean by that is that they are attempting to simulate the user interaction with the tools available (such as react-testing-library) and jump from a component to a modal, to another component just to make sure the last component displays what it should.
I find it to be a bad practice and the immediate result of this approach is having slow and complex unit tests which are very hard to maintain. In many cases these sorts of tests require the author to create an elaborate setup for it and be able to reason about it later when the test fails.
A test which relies on a “fake” application state is less reliable than a test which runs on the actual live application.
In many cases, tests tend to change the application's “fake” state and then read from it to modify a component’s behavior, but was that your intention?
If you simply wanted to make sure a component behaves in a certain way given a certain state it is not the state you are testing - it is the component.
In most cases a better approach would be to hand the “state” as an argument (prop for us React-ers) to the component.
This sort of things are where tests help you design your code better. The test “forces” you to design your component to be testable, which translates into having your component avoid side-effects as much as possible.
Sometimes you realize that the test relies on a 3rd party service call. This can be a certain library you’re using or even the browser native API.
Those 3rd parties are not your code and you do not need to make sure they work, but rather assume they work and mock them to your test’s needs.
To put it in simpler words - you don’t need a browser to have a
document object on your global scope and you don’t need to import
lodash to have a mock implementation for
Again, peeling off the irrelevant stuff from your test is crucial.
It is important to insist on asking these questions when approaching to write a new test. If you understand the type of test you’re about to write and peel off the things that are not relevant to your test, the outcome would be much cleaner, precise and efficient. It will give you better reliability and will be easier to maintain in the future.
Do you agree? if you have any comments or questions be sure to leave them in the comments below so we can all learn from them.
Hey! If you liked what you've just read check out @mattibarzeev on Twitter 🍻