Overview

This article focuses on leveraging issues during integration testing of a Spring Boot application and provides examples how to achieve efficient tests, which will prevent your application from possible integration issues in production.

Theory

Before we start, we need to understand what integration testing means. There are tons of explanations on the internet about what integration tests should actually verify.

In my opinion, one of the most accurate definitions is:

"Integration testing is a software testing methodology used to test individual software components or units of code to verify interaction between various software components and detect interface defects." -
Techopedia

Cases

Now we need to determine what parts should be covered by integration tests. Going by the definition, it should be units of code in services which interact with external systems.

Let's define the most common interaction units in the diagram below:

Best Practices

Theory is good, but what is theory worth without applying it in practice?

Since our main platform is Spring Boot, let's see what Spring provides for us in order to simplify the testing of integration units split into types from the diagram above.

@RestClientTest— anannotation which tells Spring to load needed beans for testing clients. Can only be used on beans that use onlyRestTemplateBuilder. If you want to use RestTemplatedirectly, just add @AutoConfigureWebClient(registerRestTemplate=true).

@TestPropertySource — used for adding properties to the test context. In our case, we need to add a URL for the external service.

MockRestServiceServer— a bean which is used for mocking responses of RestTemplate.

Setting up an expectation for our request.

In external service client tests, we need to assert pretty much the same things as we did in our controller test, just from the client perspective. This example covers only classes which use Spring's RestTemplate; if you use a different client like Apache HttpClient, OkHttp, Jersey, or something else, you may need to stub your responses with some HTTP server mock like MockServer or WireMock.

3. DAO Layer

To persist state services, you can use various database solutions. The most common ones are relational DB and NoSQL DB. To simplify data manipulation operation, Spring has an awesome Spring Data project, which adds a generalized interface for popular DB clients, allowing swithcing of DB providers without the pain of rewriting of all the calls. For our example, we will use relational DB, and it actually doesn't matter which one service is going to be used in production since we will use Spring Data JPA.

@DataJpaTest — this annotation disables full auto-configuration and instead applies only configuration relevant to JPA tests. It scans for @Entity classes and configures Spring Data JPA repositories. Regular @Component beans are not loaded into the ApplicationContext. This annotation also detects and configures an embedded DB if such is provided in the project dependedcies. In our test, we use H2, but Apache Derby and HSQL also could be used as an embedded DB for integration tests.

In our example, we are injecting data via repository, the simplest way of doing it, but for some cases, it's better to inject data directly to the DB without repository bean interaction. In order to do this, you may use the DbUnit library, which has a Spring support module. If you are not okay with DbUnit, take a look on Liquibase, a library not designed for tests, but which can suit your needs for injecting data into the DB before testing.

If a relational DB or Spring data repositories are not your case, don't worry — Spring provides a bunch of other autoconfigure annotations:

@JdbcTest

@JooqTest

@DataMongoTest

@DataNeo4jTest

@DataRedisTest

@DataLdapTest

etc...

DAO layer tests should assert the way your service interacts with the DB, that queries are OK, entities mappings are correct, etc...

4. Message Queue Producers/Consumers

I intentionally highlighted this integration unit since the event-driven programming paradigm is becoming more and more popular among microservices architectures, and it requires a separate paragraph.

Spring has its own adapter for interaction with message brokers called Spring Integration, which also provides testing utils to simplify the testing of integration flows.

Another library is Apache Camel, a framework for message-oriented middleware. Behind all the features Camel provides, it comes with great Spring Boot support both for production and testing aspects.

Bad Practices

Do not mix this with functional/component testing, which includes business logic verification. Integration testing should test only the integration points of your service. If for some reason it cannot be achieved, you should consider rewriting the logic; otherwise, functional/component tests should be added instead.

Avoid using the @SpringBootTest annotation, which will scan for @SpringBootApplication and load the whole service in order to test just a small piece of your application. It increases test execution time significantly. Consider using one of the provided annotations from the examples or using @ContextConfiguration to define only the needed slice of your application. Although Spring can cache test context along test suites, you should remember that it caches only identical ones, and @MockBean, for example, will force SpringRunner to create a new context with the mocked bean instead of an already loaded context. Another drawback is a case when integration testing fails and fixing it may require a lot of test runs — it can be a pain if the app contains a lot of beans and logic which should be loaded despite only a small slice of the app being needed.

Location

Unlike functional/component tests, integration tests can go along with unit tests, if they are written well — execution time should not be that high to divide them from unit tests.