Why Unit Tests Matter in the Long Run
Do you write front-end unit tests?
You might have been surprised by this question in an interview; at least I was a few years ago (well, not as much as in the above video, but still).
Unit tests are quite a well-discussed topic on the web — a lot of great articles about why we should write unit tests, however in practice, not many companies are doing it.
Recently I’ve been interviewing many front-end developer candidates and heard some common opinions why they don’t write unit tests for their front-end apps:
- “Managers won’t approve us spending time and resources for unit tests”
- “Clients won’t approve an outsourced developer to spend extra time on improving code quality “
All that matters to bosses is that we deliver our solutions as fast as possible, right? Depends on the context. If a prototype will be thrown out once presented — yes, if it will be a long-term product — no.
Unit testing opposition tends to use an argument that unit tests delay software delivery and are challenging to write, for example, mentioning web features like HTML5 canvas with requestAnimationFrame, mocking fetch requests, or using timers.
Another argument I heard is that we have end-to-end tests where the whole application is tested with some sort of automated software. For example, a solution that opens up the app in the browser and clicks through it based on a pre-defined scenario. This kind of software can check smoke tests, but how will it check that the button has changed its’ look from rectangle to circle or that there are memory leaks. These automated tools tend to rely on timings, and if some element doesn’t appear in a set amount of time, then the test fails but the reason why will make you pull out your hair in some extreme cases.
Another safeguard against unnoticed bugs is screenshot tests where a reference image is compared with actual, and it will be reported if there is a difference. It can be helpful in pipelines to not deploy merge requests to higher environments until the screenshot tests are passing.
Here is an example where a small change fails the screenshots tests. Most likely, such transformation wouldn’t have been noticed by people.
However, those tests still take quite a lot of time to run.
What about unit tests? In my humble opinion, they are the first line of defense to spot wrong behavior before even QAs get their hands on breaking your solution.
No unit tests? So what?
Let’s imagine a developer who just finished her new feature and is super excited to deliver it fast and get praise from the team and bosses. Of course, she thinks writing unit tests is a massive waste of time, and quality assurance specialists (testers) should do the testing and not her.
The developer hands over his changes for testing and …
She doesn’t realize how long it takes for a new task to be completely done after she has given it to testing. QA picks it up in a few days, writes down test cases, and makes a report of found issues, not to mention regression — to make sure new change didn’t break already existing functionality. Finally, after a few days, testing is done, and the developer sees in disbelief how many bugs have been found.
Item is returned to the developer to fix the introduced bugs. Now imagine how much time will pass until those are tested again and all bugs are fixed? A week? Two weeks? Maybe.
What could the developer have done to minimize this chance of breaking regression?
For starters, she could have written unit tests, it might have taken additional time from a few hours to a day, but she would be more confident that the code is behaving as expected. Also, in the end, many people involved will save time and deliver faster. It sounds like a sitcom, but I have seen it happen many times.
What exactly is unit testing?
When was the last time you bought software from another company? What do you usually do before buying it — you test it out. What would happen if the software didn’t work correctly? You would probably look at alternatives, similarly when a product is vastly overpriced. And how would it decrease this failing chance? We can start writing unit tests.
Unit testing is a software testing method to analyze isolated parts of source code. A unit can be many things like one utility method, component, or section of your application. Unit tests are typically automated by developers, because it requires extensive knowledge, e.g. make the code “unit-testable”.
A simple unit test consists of:
- name — The name of the test case
- setup (arrange) — prepared code to be tested
- action (act) — e.g. calling a target function
- expectation (assert) — expected vs. actual result
Example unit test
What tools should I use?
If I choose a front-end testing tool, I would strongly recommend Jest because it is well supported, actively improved, and well sponsored. It is also the most popular testing framework according to NPM data. Jest includes both a test runner and assertion tools compared to, for example, closest peer — Mocha which is only a test runner and needs an assertion library like Chai.
In the following CodeSandbox you can try out the Typescript + Jest:
If Jest doesn’t persuade you here are some alternatives:
How do we test in Evolution?
In Evolution, we take front-end tests quite seriously because of how complex our web-based games are, and we need to make sure they are in top shape before handing the games to our clients. We use unit tests, screenshot tests, end-to-end tests, performance tests, and even vulnerability tests; otherwise, it’s near impossible to achieve product excellence with big code repositories and tons of functionality.
Regarding unit tests, we use the report from the test run in deployment pipelines.
If a test fails then the code changes won’t be merged into the codebase until all tests have passed.
Jest code coverage tool
We also use a Jest included a feature called code coverage. It allows us to set a threshold for how covered the code should be with tests. If reported values are below the set margin, the final result will be counted as failed even if all the tests pass. This allows us to enforce code quality somewhat. Each team decides on the code coverage threshold themselves.
However, without code coverage, developers can just skip writing new tests and the result still will be green.
Code coverage example
Let’s extend the
Developer class a bit more to make the test fail so we can look at failed code coverage.
Time to run code coverage on the added code and see what we haven’t covered.
Jest HTML code coverage
In case the text output from Jest coverage is hard to understand there is a better way of doing it. We can generate an HTML version of code coverage and see what exactly isn’t covered. If Jest setting
coverageReporters includes the key
"html” then a folder called
coverage ( by default) will be created in the project top folder.
index.html file in the browser (under the directory
coverage ) will open up the HTML version of Jest coverage.
You can even sort the columns to quickly find uncovered files. If you click on the file you can see line-by-line what you are missing.
Getting 100% code coverage
Our coverage report shows that we are failing tests and there are 2 files uncovered with unit tests. Let’s fix that.
Now if we go back to the browser tab where our
index.html file is open, refresh the page then all the files should be now covered and in green color.
Code coverage is a good tool to improve code quality however it can become a house of cards if we only focus on meeting the code coverage threshold.
What about unit testing the UI?
Testing front-end code like utility functions usually is straight forward but what about the user interface? How do you test a React hook or if you are drawing something on HTML5 canvas?
Jest works well with front-end frameworks like React, Vue, or Angular. What Jest can use to emulate a web browser is called jsdom. It allows to validate what is rendered virtually, but it has some caveats. Fortunately, as Jest is so widely used, many contributors have made mock implementations for the missing web features in jsdom (e.g. jest-canvas-mock).
In Evolution, we mainly use React as a UI framework, and for it, there are two most popular libraries — enzyme and testing-library. They can help with rendering React components and checking the output in tests. Both work well together on the same project.
How can we improve our unit tests?
- Think critically about what is the most important thing to test in each file.
- There should be a decent amount of tests for our application’s high-impact code (base logic).
- Instead of checking if a function was called, also check with what data.
- Go from testing the smallest related unit to the biggest to harden your previous unit tests, e.g., Button → Form → Layout instead of just Layout to meet the code coverage margin.
- Use code coverage in developer teams. Decide on threshold together. This will ensure some code quality.
- Usually, it is pretty challenging to persuade a person with higher authority, especially if he has already decided when it comes to unit testing. We can patiently explain and present how unit tests will improve our daily work and products.
- If we want our products to grow and have a long life span we have to keep up the quality by regularly and extensively testing them.
- Unit tests are the fastest tests to implement for making sure software is working as expected. No matter if they are back-end or front-end tests, the same principles apply.
- Unit tests can also be used as documentation — as we are describing our tests as to how “it should” work.
- Make sure you also write end-to-end tests and much more before going to production. Unit tests are not a silver bullet to catch everything but more like an extra layer of protection.
Where to start learning?
- → storybooks
- screenshot tests
The article was written and produced by Andris Vilde.