Unit Testing Frontend Components
While I have been a big advocate for unit testing in back-end development, doing it for the front-end has been a more questionable thing. The main reason for this, being the question, what do you actually want to test? The front-end often mixes actual logic with UI and UX parts. Do you for instance want to test that a component shows a loading indicator? And if you do, do you care which loading indicator? In this blog post, I want to explore the benefits of unit testing front-end components.
Some people would advocate that you should test your front-end in the same way as a user interacts with it, as in, click on elements and navigating through your pages in that way. While I don't think this is a bad way of testing, this is the same as doing end-to-end or system testing on your back-end. It has all the same downsides as well, if one small thing is broken, a lot of tests will fail making it hard to quickly pinpoint what exactly is broken. So the need for unit testing your front-end is the same as the need for unit testing your back-end.
Moreover, frameworks such as ReactJs and Svelte focus a lot on creating re-usable components that you can re-use throughout your application. When you do this however, a small change in such a component can quickly result in changes in many different tests, which is something you do not want to do as it makes maintaining your code a nightmare. Thus the need for proper unit testing through mocking components is essential as well.
I struggled with this idea myself quite a bit as well. I always wondered whether that re-usable feedback button that shows a spinner when the process is still busy should be mocked or not. Do I not want to guarantee the spinner is present when I click it in that form? But if you take a step back, by mocking that component you still guarantee that behaviour, you just guarantee it in the place where it belongs, being the tests for that component. Your form should not have to test that the spinner is shown, that is not part of its responsibilities. And as I mentioned, code changes should not cause changes to all your tests, imagine changing the spinner into some other progress indicator. This could be a disaster for your tests.
When mocking components, it is however essential that you perform enough checks. Just checking that a mocked component is visible is not enough. You want to add checks on the props being passed to the components as well, since this will guarantee the behaviour of that component. Interactions with the mocked component can of course not be done in the same way. For instance, there is no point in clicking on my mocked button, as that won't have the logic for triggering anything. Instead, directly calling the callback that was provided as a prop is how you would trigger this behaviour.
A side-effect of mocking it like this is that you could easily swap one component for another if they have the same prop type/signature. The only thing you would have to do is simply replace the mocked component. No actual logic in your tests has to be altered.
This type of testing focuses heavily on testing the logic of your webpage and less on the actual UI. For instance, we only check that the button is present and that if I trigger it through the callback the right thing happens. But I never checked where the button is located, nor do I believe that you should. This type of fine-grained testing will easily become a burden to UI changes. Some UI things are automatically tested by the UI selectors. For instance, if my button would not be visible on the page, the locator would not find it and cause a test failure.
If you really want to guarantee a location of an element, I would strongly suggest that you check for the relative location and not a fixed location. For instance, saying that the cancel button should be left of the confirm button is something that is acceptable. Testing that the conform button is 10 pixels away from the edge of a pop-up is overkill. I personally wouldn't do this and I rely on manually checking the UI for those type of changes. Such changes are also typically not caused by some unrelated changes, as positioning is managed by the component that puts it all together.
To conclude, my view is that unit testing your front-end is essential, and just like my back-end, I don't even write full end-to-end tests of my front-end. I do focus mainly on the logic behind my components and pages, and care much less about how exactly they look in my automated tests. Let me know if you have a story or some interesting experience about testing a front-end.