Unit, Integration, and End-to-End Testing: What’s the Difference?

August 10, 2022
Written by
Reviewed by
Dainyl Cua

So you’ve written your code and it worked when you ran it – what’s next? When it comes to user-facing code, “running the code once and it worked” isn’t going to cut it. That’s why writing tests for your code is essential: testing ensures your code remains reliable even as you change it. However, there are so many different types of software tests – what testing options are out there, and how do you choose which ones to use?

In this post, you will learn about key testing types (unit, integration, and E2E tests) and the high level differences between them.

Unit, Integration, E2E Testing pyramid portrays relative amounts of each test type in a code base

TL;DR on Environments

Before we can dive into different test types, we have to understand the various environments we can run code in.

  • Local: this is on your own device. Nobody is accessing/interacting with the code except you.
  • Dev: sometimes people skip this environment. It’s basically a shared, hosted version of your code that is allowed to be broken. A sandbox, if you will.
  • Stage: this is a shared, hosted, and close copy of the production code. People deploy code to this environment before they deploy to production as a last hurrah to catch bugs.
  • Production: the code that real users engage with.

Lord of the Rings meme titled "One does not simply test in production!"

What’s a unit test?

Unit tests are your first line of defense against bugs. These tests are meant to run locally and verify the most fundamental bits of logic in your code.

Why should I use them?

Whether it be a function, a class, or anything else, unit tests are meant to test every feature/potential logical path. In fact, writing a unit test for every possible flow of your code is called 100% code coverage. Realistically, a good amount of code coverage will look something more like 80%. The more coverage you have, the more confidence you can have in your code when your unit tests pass.

When should I use them?

Always. Always, always, always. Whether you’re writing code for school or deploying code to billions of users, unit tests are a low-stakes way to quickly catch bugs.

Broom guy shouting meme labeled "Unit test all the things!"

What’s an integration test?

Integration tests check whether different chunks of code are interacting successfully in a local environment. A “chunk of code” can manifest in many ways, but generally integration tests involve verifying service/API interactions. Since integration tests are generally local, you may need to mock different services.

“Mocking” a service just means you’re simulating the input and output of a service without actually running through the full logic. Mocks are not exclusive to integration tests, but they are often used in them. To learn more about mocking, check out this blog post about mocking in Python.

Why should I use them?

Effective and scalable codebases typically consist of many small pieces of code that do their job well, but how can we tell if they are successfully working together? Integration tests allow you to verify the “glue” between your services, i.e. API calls or database queries. This comes in handy as your codebase gets larger and includes more sophisticated interactions.

Image of two drawers crashing into each other labeled "Unit tests passing. Not integration tests."

When should I use them?

Integration tests aren’t quite as ubiquitous as unit tests, but there are many reasons to use them, such as:

  • When you have services working together that were written by different people
  • For testing your app’s database interactions, or between frontend/backend of your app
  • Implementing “contract testing”, a.k.a verifying that your API is defined as expected

What’s an E2E test?

End-to-End tests, or E2E tests, are a way of verifying your code’s deployed behavior from a user perspective. You automate a user simulation that interacts with your system as a black box, so all that matters is whether the user’s actions correspond to the correct output in a timely manner. These tests are typically done in a dev or staging environment, in order to match the production user interactions as closely as possible.

Why should I use them?

E2E testing is like taking a practice exam – it’s the closest you can get to the real deal. Verifying that your code works in a near-real situation improves your confidence that your code will work in a real environment.

When should I use them?

E2E tests are complex and difficult to implement. They are most common in production-grade code, so you can probably spare yourself the time on pet projects or schoolwork if manual testing is more efficient. In summary, use E2E tests when:

  • There will be users aside from yourself
  • You have enough infrastructure for a staging or dev environment

Success kid meme labeled "It ain"t over until the E2E are over"

Summary of Key Differences

Overall, each test type has their strengths and weaknesses. When deciding on which test types to use, it ultimately comes down to whether its purpose/common use cases suit you, and whether their properties (i.e. execution time, required technical infrastructure, etc) are viable for your system.

See the chart below for a summary of each test types’ similarities and differences.

Triple venn diagram for Unit Tests, Integration Tests, and E2E with pros and cons in each section

What’s next?

Now that you know what kinds of tests are out there, go reinforce your code! For more details on how to actually implement unit, integration, and E2E tests, feel free to peruse the following resources:

Happy coding!

Michelle Tran is a Software Engineering Intern on Twilio Flex’s CLAW team during the summer of 2022. She is currently studying Computer Science at the University of Colorado Boulder, and can be reached at mtran [at] twilio.com or on LinkedIn.