User-driven testing in Frontend web development 🧑💻
In this post, I would like to write about my experience concerning software testing in Frontend engineering. Stay tuned and read on guys 😊
First and foremost, everything starts with the WHY. The ultimate goal of writing tests is to ensure the success of our projects in the future. Yet, A LOT of us have been struggling with knowing what to test in our daily development. I am no exception, I started FrontEnd development back in 2017. At that time, many modern tools for web development did not exist. Most of the companies I worked for during that time either relied on their manual QA's sharp eyes or automation tests from Selenium which is ridiculously slow yet very reliable. Most developers I worked with did not know how to write tests effectively. Angular and React are the most famous UI libraries/frameworks back then. Even they did not have any documentation about software testing best practices for Frontend web development.
As far as I remember, Enzyme was the most popular solution for testing React components/applications back then. I tried it and never felt the fun of writing software for humans anymore. The API surface is ridiculously large. The more I learned about it, the less confidence I felt about my capability in web development due to its unnecessary abstraction. Interestingly, react-shallow-renderer
made its way to the official React repository here 🙈
Entering Testing Library era. Kent C. Dodds has always been one of my virtual mentors since day one of my career along with others. I was there first when he first prototyped react-testing-library (old name of @testing-library/react 😄). I tried it out and things clicked in my mind.
The more your tests resemble the way your software is used, the more confidence they can give you.
When I write tests in testing-library, I feel empowered by all the utilities it gives me. Underneath the hood, it's trying to expose the platform to us instead of hiding them away. For instance, when you read dom-testing-library
, it is merely using the web platform API to mimic exactly how users interact with the web. It's just a bunch of events being dispatched and handled at runtime. Thus, by reading the source code, I have learned more about the web platform 🥰
Yet, it's only part of the story. Even the "best" tools in the world cannot stop us from writing bad tests. Good code is like a love letter to the next developer who will maintain it. Tests are also code which is often neglected by most developers I have worked with 🥲. Some companies are even aiming for 90+ or event 💯 code coverage before shipping code to production. Yet, they are still suffering from bugs - I'm going to demonstrate below why 100% code coverage is far from enough to be confident in shipping code to production. In short, 100% code coverage merely means your shipping code is run by your test runner(s) - nothing more 😱 It does not indicate that 100% use cases are well covered if the tests are not well-written 🥹
Let's start with a user requirement followed by several test iterations to improve our confidence over time before shipping the code to production 😎
- Given Nam lands on the home page, he should see a form with a name input and a submit button
- When Nam types his name and submits the form
- Then he should see the message
Hello Nam
The image below demonstrates how it is supposed to work in production 😊
The first version of our shipping and testing code looks like this:
I chose to use React since it's the most popular among FE devs. Any UI library or even vanilla JavaScript should work just fine. And Enzyme was used to prove the not-so-good API for testing. Needless to say, we just reached 100% code coverage. Imagine it's a checkout form, you and your co-workers are supper excited about shipping this to production and celebrate 🎉
A few moments later, production deployment finished, and...
Huh? What's going on??? As it turned out, our tests did not resemble the way users interact with our software AT ALL. In this case, the button will not trigger the form submission since its type is a button that does nothing when pressed by default 🤯
Let's say I was a software testing consultant which I am not 🙈 I came in bravely and looked at the 100% code coverage codebase. What I care about is how well the story has been written by tests. As of now, all I see is just a mock, shallow, simulated, etc It's not how users interact with the software. Let's put on the hat of the QA guy and act like one. Given the requirement above, they would write some kind of e2e tests like this
😮 The test is much better now, isn't it?
First, it makes sure the page is accessible automatically via axe-core tool. Then it acts like an end-user and makes assertions along the way to the end. There are many great points covered by this kind of test:
- Implementation detail free - technologies agnostic
- Resilient against changes - it only changes as per user's requirement
- Super reliable - it uses actual browsers instead of simulated environments like JSDOM
Now, if you look back at our tests, we can see a ton of problems there. We didn't resemble the ways our software is being used but rather expected certain things have been called (implementation details) and mock things along the way. The more we mock, the less confidence we have - please make sure only mock when you have to aka The merits of mocking
Yet, e2e has only one drawback - it's slower than node-based tests since it requires a full-fledged environment to test. Yet, many experts found that e2e tests are underrated, and it's still acceptable at scale even at Google 🚀
For those of us who are still concerned with the speed of e2e tests and are already immersed in node-based testing. I highly recommend trying testing-library
family. It supports many UI libraries like React, Vue, Angular, Preact, etc. And they are recommended even from the library's official page as well 🎉
Let's re-write our Enzyme test in testing-library/react
and see the difference 😉
Let's revise a couple of improvements we just made with the test:
- Our test is almost implementation detail-free - we render the
App
component to the DOM using a utility fromtesting-library
🎉asdasd - We make sure the output is fully inspected - the actual output of any UI libraries is the interactive DOM - not whatever components/templates/nodes/etc in their dictionary. At the end of the day, users only interact with the only web platform that accepts HTML, CSS, and JavaScript 😇
- It tells exactly the story of how our software is consumed by end-users - whoever maintains this application will not have a hard time trying to figure out what the application does in production. It's a win for every stakeholder - aka 💯 use case coverage
Yet, we did make one huge trade-off here. It's a simulated node-based browser environment. That's why I had to comment out a block of code which supposed to be passed ✅ but did not. It's passing in e2e tests since it's using a real browser. Everything is a tradeoff 🙈
Please feel free to play around with the code on GitHub 😊
There you have it guys, I hope this can help you write good tests for your users/companies/future teammates, etc. Together, we make the world a better place through quality software 💞