Skip to content

Unit, Integration, or E2E? Finding the Right Level of Testing

I see it all the time in pull requests: debates over how a test should actually be written.

3 min read
Unit Testing
Integration Testing
E2E Testing
Vitest
Playwright
Frontend

I see it all the time in pull requests: debates over how a test should actually be written. It’s the same classic question: "Is this a unit test, an integration test, or should we just use Playwright?"

The truth is, picking the wrong testing layer can be worse than having no tests at all. If you get it wrong, you usually end up with one of two things: tests that fail to catch actual bugs, or tests that actually get in your way when you try to refactor code that isn't even broken.

I don't have a strict formula, but I do have a few questions I ask myself before I start writing test code.

First, is the behavior pure? If the code doesn't need a React tree, a network connection, or a router, then unit tests are the safest bet. But if the issue involves "connectors" like providers, query keys, or form libraries, I feel like integration tests using Testing Library make more sense.

Then I ask: "If I use fake collaborators, will I actually be hiding bugs?" If the logic involves crucial things like auth cookies, page navigation, or third-party redirects, then E2E is the answer. I also look at how often the UI changes just for aesthetic reasons. If the visual layout shifts constantly, I prefer not to be too strict with assertions on the HTML structure.

One mistake I see a lot is focusing too much on unit tests. We feel safe because our code coverage is high, but then bugs start popping up in production within the "glue" that connects functions. The logic of a single function might be perfect, but at the component level, the error message might be hidden behind another element or broken for accessibility.

On the flip side, relying too much on E2E has its downsides too. If you have to restart the entire app in a browser every time you change a single checkbox, your CI process is going to feel incredibly slow. Eventually, developers get tired of waiting, and if tests become flaky, people just stop trusting the results altogether.

To put it simply, imagine we're building a "cancel order" feature.

To make sure the data format being sent to the API is correct, I'd use a unit test. To ensure the button becomes disabled during the process and shows the right error message, I'd use an integration test. Finally, to make sure the whole flow works, from the user clicking the button to the order list page updating automatically. That’s where I’d use E2E.

One thing I've learned: not everything needs to be automated. If it's just about visual polish, it's probably better to check it manually or with a specific tool rather than weighing down every single pull request. And if a teammate says, "we need to increase our coverage percentage," I usually find it's better to stop and focus on the actual failure scenarios a user would face.