Introduction
The first time I entered a company as a developer, I did it as a sort of full stack, more oriented to Front End than Back End. At that moment, I was assigned to those who later became my very good mentors. Their role were basically sit next to me, and teach me the company rules, the project code, and algorithmic concepts that, for my zero experience, and my until that moment, academic formation, I don't knew very well.
One of that concepts, were "testing". Although I had an idea of what it meant, and I had interacted with it in the past years, I had never done automated testing from scratch, much less involving the company's tools and languages at the time (JS, Jest, Vue).
So, little by little, with the passing of the tasks, I was learning to do this, giving as results tests of the style:
// ExampleComponent.vue
// ExampleComponent.spec.js
Note that this test is an approximate and might not work, the important thing here is that you get the point. Here I'm using Vue as a Front End library https://vuejs.org/ and jest as a tool for testing https://jestjs.io/
The problem
Yes, this is a correct unit test, which covers the script
tag of the example Vue component, and, in a way, gives us some security... it's better than nothing. But, with the passing of the developments and the use of the system by the users, it became more and more clear that this type of test did not cover all the possible errors.
In fact, the test is covering the behavior of the init
method and dataToShow
computed, but, we can see that we aren't testing the connection between the Vue component and its store, and we aren't testing the reaction of the component's template when the state of the variables change. In conclusion, we aren't close to simulate the user's behavior in the view.
Something I have learned as I have gained experience, is that while the more close is the test to the user, and the less mocks it has, the better.
While a unit test, tells us if the code of some method runs in an expected way, an integration test tells us if a complete use case behaves in an expected way, resulting in a stronger, safer, and more reliable test.
And believe me, in my case, this type of testing has caught bugs in situations that a unit test hasn't.
The solution
So, at the end, how can we do a test like that?
Well, the key here is to think on how we can convert the manual actions that the user does in the view, in automated actions in our test.
For the example we saw before, we have the following manual actions:
- Open the view
- Press the init button
- View the information once it is loaded
So, how can we convert the previous test to simulate this manual actions?
In this Vue concrete case, we can do it using the Vue Test Utils https://v1.test-utils.vuejs.org/ (every library/framework has its own tool for this):
Note that this approach force us to make identifiable the elements we are testing. Because of that, we refactor the example Vue component as follows:
Now, through this test, we are ensuring the correct behavior of the complete use case that we saw before, since we are simulating the actions the user would do in the view. And now, we have a more coverage (Vue script, store, Vue template) from a test that has fewer lines of code. This is great, isn't?
Some cons and their solutions
Right, from my experience, this type of test come with a couple of cons:
- They are more difficult to build, because now, we have to think how can we test a complete use case, instead of think how can we test some lines of code from a method. This leads us to think in how can we do the manual actions of the user in an automated test, and, how can we test more lines of code together.
- Since we are using the template to perform actions and asserts, there are troubles with the time of the render. A correct test might fail randomly, because some part of the component is not render yet for the next action or the next assert.
For the first cons, the solution is very simple, just get practice until this type of test become more natural.
For the second cons, after a lot of research, I find a very solid solution, through this method:
export const retry = (assertion, {
interval = 20, timeout = 1000,
} = {}) => {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const tryAgain = () => {
setTimeout(() => {
try {
resolve(assertion());
} catch (err) {
Date.now() - startTime > timeout ? reject(err) : tryAgain();
}
}, interval);
};
tryAgain();
});
};
This method repeat an action in a period of time that cannot be measure for us, the humans, and allows the component to finish its render. It's like perform an await wrapper.vm.$nextTick()
the necessary times until the component reach the render of the previous action, without increase the running time of the test. This way, we create a test that doesn't depend on the render time of the component. If the test is correct, then pass, if the test isn't, then doesn't pass. Simple.
Now, we refactor our test to remove the second cons:
Conclusion
I hope you enjoyed this article and, more importantly, that you learned something new. Remember, this (like everything in programming) is all about context, in some cases it's better to do unit tests, in others it's better to do integration tests, or do both, It depends on you.
Any opinion/suggestion/improvement is welcome, please comment below!
Thanks so much for reading, happy code!
Top comments (13)
Your headline of "stop doing component unit testing" is terrible advice. Testing small self-contained units is the best way of quickly detecting and finding regressions.
It just shouldn't be all you do.
Yes actually I recommend do it if the context requires that. What I mean is that, under my experiencie, an integration test should be in the first priority.
But this is a personal approach, this is not an absolutely truth.
Cheers!
This is exactly my experience, too. If you cannot afford to build full automated test suite with unit tests and integration tests, skip the unit tests.
Unit tests are nice because they will pinpoint the source immediately but they are rarely good enough and will not catch bugs from interactions between units.
Unit tests alone might work if you have 100% coverage and active mutation testing to verify that your tests test everything. I've never been in a team that could do that in real world.
Yikes. Please don’t skip unit tests. Test coverage is a progression and you make trade offs based on the quality of your tests. Unit tests have the lowest barrier in terms of creating confidence in your code.
Don’t skip testing. Just write better tests.
As I wrote, if you cannot write both unit tests and integration tests, skip the unit tests and write integration tests only.
Of course, having both is obviously better. If I could choose, every piece of software would have full test suite with 100% code coverage and mutation testing to validate the test suite. (Mutation testing is about testing the quality of the tests, not the program.) However, sometimes the employer wants to go with "good enough" implementation and take a risk with unknown bugs instead of paying for the test suite.
I’m the opposite. If you can’t write both, write unit tests. The feedback loop is just faster. Integration tests just require more complexity upfront. For me confidence is the currency, and unit tests just pay out sooner.
The title of the article should be "Stop doing ONLY component unit testing."
You should read about the testing pyramid.
In fact we need both small unit tests and large end-to-end tests as they cover different part of the system with different angles.
All those tests should pass all the time.
I was brought recently to a similar conclusion when building a Laravel + Inertia + Vue application. Inertia comes with a nice integration to write this style of test inside where a back end dev would expect to write their unit/feature tests. Essentially it blends the styles of Jest and PHPUnit and it was quite a nice and intuitive syntax to use.
After writing a couple I was like, "huh... these are much more 'simulating the user' type tests than I usually write for my server-side code" so I definitely see the value of adjusting your thinking when approaching test writing for components in Vue.
Wow, thanks so much, I'll investigate that tool, it seems really helpfull!
This video opened my eyes about unit tests.
Yes he talks about test driven development, but he explains the core concepts of what is a unit, what is a test and what a test should check.
youtu.be/EZ05e7EMOLM?si=oO_ohtTuG1...
A nice solution is cypress component testing.
I like the idea of testing components in a browser.
Totally, it is something that I am iterating too, it seems really nice