Skip to main content

Integration Tests

Introduction

Integration Icon

There are some things that unit tests can't catch. Certain defects will only appear when you combine multiple pieces together. We need more complex tests in order to answer questions like:

  • Does the database work with the application?
  • Am I using the right format for my request?
  • Is the database accessible?

For these types of questions, you need integration tests. Integration tests allow you to test the boundaries between modules or components. They are more complex than unit tests and require more setup, but they are essential for ensuring that your application works as expected.

Types of Integration Tests

There are two main types of integration tests that are used for vastly different purposes. Having two totally different meanings for the same term can be confusing, so it's important to understand the difference between the two. As always, there is no universally accepted definition, so use the definitions common to your team.

Component integration tests are internal tests that check the interactions between components within a single application. On the other hand, system integration tests are there to ensure that you are interacting with some external system correctly. These tests are slower and more complex than component integration tests, and often less consistent.

You should be writing both types of integration tests during development. Component integration tests act as a simpler form of acceptance testing, while system integration tests let you validate your assumptions about the behavior of external systems.

Component Integration Tests

Component integration tests are internal tests that check the interactions between components within a single application. They commonly mock out external dependencies, such as databases or APIs, to ensure that the test is consistent and repeatable. These tests are often run as part of the build process to ensure that the application is working as expected. These might also be called component tests, API tests, or interface tests.

This type of test is very similar to a sociable unit test, but is allowed to access system resources and sometimes external dependencies. They tend to run more slowly than unit tests, but are still relatively fast compared to system tests.

Objectives

The main goal is to identify misunderstandings about how the various internal components interact with each other. This is especially important when working with a team, as different developers may have different assumptions about how the components interact.

You will often be writing tests that cover code written by other developers, so it's important to have a good understanding of the codebase.

Best Practices

When writing component integration tests, you should follow these best practices:

  • Have a Plan: Before writing the test, have a clear idea of what you are testing and why. Don't try to validate the entire application in one test.
  • Use Mocks: Mock out external dependencies to ensure that the test is consistent and repeatable.
  • Keep It Simple: Focus on the interactions between components, not the internal workings of the components. Leave that to the unit tests.
  • Run Fast: These tests should be fast, so you can run them frequently during development. Each test should run quickly, but you may need some startup time to set up the environment.
  • Use Real Data: Use real data when possible to ensure that the test is as close to the real world as possible.
  • Log Extensively: If the test fails, you need to know why. Make sure that you are logging enough information to diagnose the problem.

System Integration Tests

A system integration test is there to ensure that you are interacting with some external system correctly. This could be a database, an API, or another application. These tests are slower and more complex than component integration tests, and often less consistent.

When writing this type of test, you should not be concerned with the internal workings of the system. Instead, you should be focused on the inputs and outputs of the system. Because this sort of test requires systems outside of your control, you don't want to be using the to test the bulk of your application. Instead, use them to ensure that your application is interacting with the external system correctly.

Objectives

The main goal of this sort of test is to validate your assumptions about the interfaces and behavior of external systems. You want to ensure that you are using the correct format for your requests, that you are handling errors correctly, and that you are getting the expected results. They streamline development by catching errors early and ensuring that you are using the external system correctly.

Ideally we'd always have good documentation and well defined APIs and contracts, but in the real world that's not always the case. These tests help to catch those issues early.

After development is complete, these tests can be used as a smoke test before a release. They ensure that the API hasn't changed and that the external system is still responding as expected.

Best Practices

When writing system integration tests, you should follow these best practices:

  • Keep It Simple: Focus on the inputs and outputs of the system, not the internal workings of the system. Since you can't control the external system, focus on the simplest cases.
  • Clean Up After Yourself: Make sure that you are cleaning up after the test. You don't want to leave a mess behind. Since you are actually calling the external system, you may need to undo any changes that you made.
  • Run Less Often: During initial development you will be running these tests frequently, but once the system is stable you may want to disable them. They are slow and can be flaky, so you don't want to run them every time you make a change. Use them as a smoke test before a release, but not as part of your regular development process.

Testing Strategies

There are several different ways to approach integration testing. These strategies dictate when integration tests are created and who is responsible for them. The most common strategies are:

  • Top-Down Testing: Start with the highest level of the application and work your way down. This is a good way to catch integration issues early, but can be difficult to implement if the lower levels of the application are not yet complete. Initially you will use test doubles for the lower levels, but as development progresses you will replace them with the real components.
  • Bottom-Up Testing: Start with the lowest level of the application and work your way up. These lower level modules are often easier to test, with limited dependencies. This is a good way to catch issues early, but leaves some of the most important integration issues until the end. You will need to use some sort of test driver to simulate the higher level components. The most crucial logic, the parts that the user interacts with, are tested last.
  • Big Bang Testing: Wait until all of the components are complete and then test them all together. This is the fastest way to write integration tests, but can be difficult to debug. You will need to test the entire application at once, which can be overwhelming. This is often used in legacy systems where the components are already complete.
  • Sandwich Testing: Start with the highest level of the application and work your way down, but test the lower levels as you go. Use both stubs and drivers to simulate the components that you haven't written yet, and swap them out as more tests are finished.

These strategies are written with the idea that you should have a complete suite of integration tests, and that is rarely practical. It is better to be thorough with your unit tests and use integration tests to cover the most important subsets of your application.

Conclusion

Integration testing is more complex than unit testing, but gives more confidence that your application is working as expected. There are two main types of integration tests: component integration tests and system integration tests. Component integration tests are internal tests that check the interactions between components within a single application, while system integration tests are there to ensure that you are interacting with some external system correctly.

When writing integration tests, you should follow best practices to ensure that the tests are consistent and repeatable. You should also follow a testing strategy to ensure that you are testing the most important parts of your application.

Image Credits

Integration icons created by Muhammad Atif - Flaticon