Writing Integration Tests: Part I

Viraj Turakhia
Decaffeinating Java
3 min readJan 24, 2021

--

We have a monolith and a few months ago our test pyramid looked like this:

We had a lot of unit tests, more end-to-end tests than we would have liked and zero integration tests. We fixed our test pyramid by writing more integration tests and by reducing the number of end-to-end tests. The pyramid is near perfect now.

In this article, I talk about the need of writing something more than the unit tests and the end-to-end tests, our strategy to identify potential components for integration tests, and the advantages of writing more integration tests.

Why Integration Tests?

To understand why we felt the need of writing more integration tests, let’s look at our typical REST endpoint:

In our typical REST endpoint

  • The first step is to validate the input parameters. If validation does not succeed then the endpoint returns 40x.
  • If the validation succeeds, a business logic library is called to perform the core operation. If the library throws an exception then we return 40x.
  • If no exception is thrown so far, the next step is to call a second party library to decorate the output.
  • And the last step, irrespective of whether endpoint passes or fails, is to log the call.

We wanted to automate the test case when the input validation fails. In the test case, we wanted to ensure that the right exception is thrown, logging is done, and a right 40x status code is returned. The code flow is highlighted below.

We could not write a unit test because it crosses the class/component boundaries. Input validator and logger are separate software components.

We started with an end-to-end test but we soon realized that it is wasteful and expensive because it requires:

  • A running app and dependent services
  • A populated DB
  • Setting up the resources to call the endpoint

The test case that we were trying to automate did not need any of this. Operations performed in the test case were all executed in memory. And this is where we identified that gap in our testing strategy. We needed something that can test more than one unit at a time. Something that does not need a heavy lifting of starting the app and populating the DB.

Our First Integration Test

Our first integration test looked like this:

  1. Instantiate the class which is handling the REST call
  2. Call the endpoint with an invalid input
  3. Validate the 40x returned value
  4. Validate that the logging of endpoint call

The test looked like this:

public class GetRegisteredUserIntegrationTest {

@Test
public void testInvalidInput() {
CustomLogger logger = new CustomLogger(mock(Logger.class));
RegisteredUserResource rsrc =
new RegisteredUserResource(logger);
GetRegisteredUserInput input =
new GetRegisteredUserInput("invalidId");
GetRegisteredUserOutput output = rsrc.get(input);
assertTrue(output.getStatusCode(), 400);
verify(logger, times(1)).logEvent();
}
}

Defining Integration Tests

Our flavour of integration test is a hybrid between the unit test and the end-to-end tests. Our flavour of Integration Tests:

  1. Can be executed without a running app (very much like a unit test)
  2. Executes more than one software component at a time
  3. Tests the interaction between multiple software components
  4. Runs FAST!

In the next part, I write about how we identify the software components to write integration tests and the advantages of writing integration tests.

--

--

Viraj Turakhia
Decaffeinating Java

A software engineer with 17 years of experience and still learning.