Integration tests vs. unit tests: Why integration tests matter more
In the hierarchy of things developers like to do, writing tests falls at about the “necessary evil” level. Like eating a heaping pile of spinach, we know it’s good for us–but getting motivated to do it can be a struggle. Of course running the proper tests saves us a whole lot of time and money fixing bugs down the line, but even then there are usually a million other things we’d rather do than write tests.
Luckily for us, many other software developers also didn’t like doing it, so they created lots of automated testing tools to make it easier. These days you can find a tool for just about any testing type and programming language.
Unit, integration, and static testing tools tend to be more language-specific. Popular tools like JUnit, Jest, pylint, mocha, and Visual Studio’s built-in unit test feature make it easier to automate your testing set-up.
But no matter which tools you decide to use, it’s essential to know how — and when — to use them.
Different tests for different purposes
Usually, the choice between what test to use boils down to the tradeoffs between the time/costs of running that test and your confidence in the test results. We’ve graded four testing types—end-to-end, integration, unit, and static—on a scale of 1-4 💲(cost/time) and 👍(confidence) to help you understand when to deploy each.
End-to-end testing tests the application from start to finish. It tests the entire user journey of an application, mimicking the quality issues a user might encounter while trying to carry out an action in your application. Generally, these tests are the most time & money intensive but will also give you the most confidence that your application won’t bug out on users.
End-to-end tests are expensive because they require more heavily powered servers to spin up and take longer to complete (time = money).
They take so much longer because instead of mocking out the backend, they make calls against a real server. While this is good because it can catch a lot, it’s bad because if an end-to-end test fails, it may not be because of the frontend’s code but maybe because the backend is having problems.
Integration testing tests interactions between components – think between backend services or components in a frontend framework.
These tests are necessary to make sure components work correctly together – your unit tests may show that each part works correctly, but there may be data or logic errors that only show up when the components are tested outside of isolation.
Integration tests are cheaper since you can spin up a much lighter process to run the test but still catch many potential issues. We’ll discuss more why you should focus on integration testing below.
Unit tests don’t test integration with any other part of the code but instead centralize on a specific bit of code and make sure a particular set of inputs match a set of outputs – like conditional logic and function calls.
Because of this focus on smaller bits of logic and data processes, they’re great for testing libraries.
Static testing is your basic linting or static code analysis. It helps you find fundamental issues like typos and missing characters. Static tests are the quickest and cheapest test to run but won’t give you much confidence in the quality of the entire application.
Static tests are commonplace and utilized in almost every IDE and every language.
The understated importance of integration testing
Prominent software engineers like Kent C. Dodds of Remix.run and Guillermo Rauch of Vercel advocate for a bigger emphasis on integration tests since they cost less than end-to-end testing but cover more than unit tests.
To help explain his viewpoint, Dodds created the “testing trophy” – an updated version of Martin Fowler’s “testing pyramid.”
|Testing Pyramid||Testing Trophy|
Fowler’s pyramid is pretty well known – it shows that as costs rise, the speed of a test tends to decline, hence why many developers minimize E2E testing and maximize unit testing. The testing trophy, on the other hand, is a bit less obvious, so here’s how Dodds describes it:
The general idea is that you get *great* value for the cost out of static analysis tools and integration tests. Then you get *good* value out of unit and e2e tests. Use them all.Kent C. Dodds
Part of the great value of integration tests is that they still test user behavior like end-to-end tests. Users will rarely be using components in isolation, rather most software runs as a series of interactions among different components — and it’s exactly these interactions that integration testing is verifying. This makes them a confidence improvement on unit tests, which simply test inputs and outputs of a particular piece of logic. Or, to put it another way:
It doesn’t matter if your component <A /> renders component <B /> with props c and d if component <B /> actually breaks if prop e is not supplied. So while having some unit tests to verify these pieces work in isolation isn’t a bad thing, it doesn’t do you any good if you don’t also verify that they work together properly. And you’ll find that by testing that they work together properly, you often don’t need to bother testing them in isolation.Kent C. Dodds
Of course, this is only true of well-written integration tests.
What makes a good integration test?
You should try to mock out as little as possible with good integration tests. You want to maximize the amount of application functionality that you test.
This is not a natural testing inclination, especially if most of your testing experience has been in unit testing. A notable example of this in frontend development is the predisposition to creating “Shallow Render” tests.
Shallow Render tests only test a single component rather than the interaction of multiple elements on a screen. But this tends to be bad practice since it won’t catch issues with the integration between multiple components.
If you’re one of the developers with a bias towards mocking and shallow rendering, you’ll have to pay extra attention to make sure that your integration tests actually test interactions between your different components.
There’s a trend towards emphasizing integration tests as part of an overall testing framework, and there’s a good reason it’s not going away anytime soon:
Integration tests strike a great balance on the trade-offs between confidence and speed/expense. This is why it’s advisable to spend most (not all, mind you) of your effort there.Kent C. Dodds
A comprehensive testing strategy will use a combination of the four different testing types; however, it’s hard to beat the bang-for-the-buck that integration tests provide.