Common mistakes in testing UI components and how to fix them in 5 minutes (Vue.js)
Have you ever had to write a new component in Vue and, once the code was done, you tell yourself:
Damn I need to unit test it now.. Where do I even start?..
At least that used to be me every time in my newbie days with Vue. So if you’re like me or at least want to see how much you actually know about testing UI components then keep on reading :)
I want to keep it simple for you so the article is made of a few lists of DOs and DON’Ts and some practical tips on how to structure UI components tests.
UI components testing principles
Here’s a list of important principles I abide by when writing unit tests for UI components, be it React, Vue or any other library. There might be more but these never let me down:
- Think about tests as the dynamic documentation of the component. Comments around code can get outdated quickly but the unit test will be updated together with the component’s functionality so they never get out of date.
- Behaviour-driven testing (BDT): Organise the test suite in a way it clearly illustrates what the component does and not how it is done (more details in the section below).
- Treat Vue components as a black box: a series of inputs (props, user actions, data stores) that produce a well-determined series of outputs (events, renderer output, function calls). These constitute the component’s public interface or the component contract and represent the core focus of our unit tests:
- By focusing on testing the component contract we avoid testing the implementation details. The latter is prone to change and make our tests brittle and hard to maintain.
A “real world” example
Let’s take a real-world example of a simple registration form component.
All it does is to receive the user's email as an input element and when the “Submit” button is clicked it calls an API to register the user provided the “Terms and Conditions” were previously accepted. Pretty basic stuff, here’s the short version of the code (you can find the full code here):
DOs and DONTs
Let’s now try to write the unit tests for this component together. As promised, I want to keep it simple and informative so here’s another list (yes I like lists as you might have noticed), this time DOs and DONTs to have in mind when deciding what to test and what NOT to test.
1. Test the component contract and not the implementation details
🚫 DON’T assert over implementation details
…meaning the data properties, computed properties or Vue methods.
These are private to the Vue component so if a developer wants to refactor some code without even impacting functionality they will have to change the associated test. Even a simple rename of a computed prop or method would lead to a test failure, making it very brittle and a nightmare to maintain.
NOTE: If you’re coding in TypeScript, the compiler will help out and will display an error if you’re trying to access private members.
🚫 DON’T test computed props or data props:
🚫 DON’T test Vue methods:
✅ DO assert over the component contract:
… meaning rendered output, boundaries with other components, emission of events and calling external functions (Vuex actions, APIs).
All of the above are public and part of the contract the component has with the outer world.
✅ DO test rendered output:
✅ DO test events:
✅ DO test calls to external functions (Vuex actions, APIs):
Behaviour-driven testing (BDT)
🚫 DON’T create a cluttered test suite with a flat structure
Don’t create tests that are not ordered in a way that logically describes the component. They become difficult to understand, maintain and build upon.
✅ DO group tests by behaviours (BDT) and preconditions
- group tests (using
beforeEach) by the tested behaviour and preconditions (e.g.: feature toggles, the state of a store that is passed as a prop or through a getter, etc)
- use nested
describestatements for better grouping (not to be overused)
Test a single concept in each test function
🚫 DON’T create tests with many assertions (ideally just one assertion per test)
✅ DO split tests using your testing framework’s capabilities
Try to split test using the following techniques:
- use nested
describestatements to improve readability
- put common setup code in
- reduce the number of assertions in each test, ideally to one
Use descriptive sentences when writing test specs
🚫 DON’T use names of internal properties
Don’t use names of internal methods, computed props or other implementation details.
These are prone to change quickly and create an unnecessary dependency between the component and the test. Moreover, it shifts the focus to testing implementation details rather than the component’s public interface.
NOTE: Method names can be used in describe statements if they are part of the public interface of the tested unit (e.g.: public member in a class).
✅ DO use sentences that describe the behaviour of the component
describeclause to describe the precondition of the test suites or the scenario
shouldor a verb (
calls) for an
test) clause to reflect the expected outcome
By going through all these tips and tricks, I hope it became a bit more clear to you on how to test UI components, what to focus on and how to structure the test files. And if not, please let me know in the comments section how I could make it more clear.
If you really gave up but you made it to the end, here’s a small present for you: the RegisterForm.spec.ts working test file taking into account all the aspects of the article.