Writing Integration Tests for Spring Boot Web Applications: Build Setup

When we want to write integration tests for Spring Boot web applications, the first thing that we have to do is to create a build that makes our life as easy as possible. In other words, we must create a build that allows us to decide what happens when we build our project. This blog post describes how we can create a Maven build that fulfills this requirement.

After we have finished this blog post, we:

  • Can identify the useful Maven profiles.
  • Know how we can create the required Maven profiles.
  • Understand why we should put different tests in different directories.
  • Can create separate test and resources directories for unit and integration tests with Maven.

Let's begin.

Creating the Required Maven Profiles

If we want to create a build that allows to decide what happens when we run it, we must be able to:

  • Select what kind of tests (unit, integration, end-to-end, and so on) we want to run and run only these tests. This gives us the possibility to keep our feedback loop as short as possible because we can run only the tests which are useful to us.
  • Run all tests. This allows us to create a CI/CD pipeline which ensures that our application is working as expected.

We can fulfill these requirements by creating three Maven profiles:

  • all-tests. Maven runs both unit and integration tests.
  • integration-tests. Maven runs only integration tests.
  • unit-tests. Maven runs only unit tests. This profile is enabled by default.
This blog post assumes that we have only unit and integration tests. If we would have other tests (such as end-to-end tests), we would need a new Maven profile that runs only our end-to-end tests. Also, the all-tests Maven profile would run our unit, integration, and end-to-end tests.

We can create these Maven profiles by following these steps:

First, add two new properties to our Maven build:

  • If the value of the skip.integration.tests property is true, Maven shouldn't run our integration tests. The default value of this property is true.
  • If the value of the skip.unit.tests property is true, Maven shouldn't run our unit tests. The default value of this property is true.

After we have added these properties to our pom.xml file, its properties section looks as follows:

<properties>
	<!-- Other properties are omitted on purpose -->

	<skip.unit.tests>true</skip.unit.tests>
	<skip.integration.tests>true</skip.integration.tests>
</properties>

Second, add the required Maven profiles to our Maven build by following these steps:

  1. Add the unit-tests profile to our Maven build and ensure that this profile is active by default. Remember to ensure that Maven runs only unit tests if this profile is active.
  2. Add the integration-tests profile to our Maven build and make sure that Maven runs only integration tests if this profile is active.
  3. Add the all-tests profile to our Maven build and make sure that Maven runs both unit and integration tests if this profile is active.

After we have added the these Maven profiles to our POM file, its profiles section looks as follows:

<profiles>
	<profile>
		<id>unit-tests</id>
		<activation>
			<activeByDefault>true</activeByDefault>
		</activation>
		<properties>
			<skip.unit.tests>false</skip.unit.tests>
		</properties>
	</profile>
	<profile>
		<id>integration-tests</id>
		<properties>
			<skip.integration.tests>false</skip.integration.tests>
			<skip.unit.tests>true</skip.unit.tests>
		</properties>
	</profile>
	<profile>
		<id>all-tests</id>
		<properties>
			<skip.integration.tests>false</skip.integration.tests>
			<skip.unit.tests>false</skip.unit.tests>
		</properties>
	</profile>
</profiles>

Third, add the Maven Surefire plugin to our Maven build and ensure that it doesn't run unit tests if the value of the skip.unit.tests property is true. After we have configured the Maven Surefire plugin, its configuration looks as follows:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-surefire-plugin</artifactId>
	<version>3.2.5</version>
	<configuration>
		<skipTests>${skip.unit.tests}</skipTests>
	</configuration>
</plugin>

Fourth, add the Maven Failsafe plugin to our Maven build and ensure that it doesn't run integration tests if the value of the skip.integration.tests property is true. After we have configured the Maven Failsafe plugin, its configuration looks as follows:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-failsafe-plugin</artifactId>
	<version>3.2.5</version>
	<executions>
		<execution>
			<id>integration-tests</id>
			<goals>
				<goal>integration-test</goal>
				<goal>verify</goal>
			</goals>
			<configuration>
				<skipTests>${skip.integration.tests}</skipTests>
			</configuration>
		</execution>
	</executions>
</plugin>

Next, we will learn to configure the source and resource directories of our unit and integration tests.

Configuring the Source and Resource Directories

I argue that we should put different tests to different directories. In other words, I think that our unit and integration tests should have different source and resource directories. If we follow this approach, we know exactly where we can find our unit and integration tests. We don't have to use artificial naming conventions or waste any time wondering whether a test class contains unit or integration tests.

If we write only integration tests, we should put our integration tests to the src/test directory and run them by using the Maven Surefire plugin.

By default, Maven expects that:

  • Our test classes are found from the src/test/java directory. In the context of this blog post, this directory contains the source code of our unit tests.
  • The resources of our tests are found from the src/test/resources directory. In the context of this blog post, this directory contains the resources of our unit tests.

Because we want to create a new directory that contains the sources and resources of our integration tests, our Maven build must fulfill these three requirements:

  • The src/integration-test/java directory must contain the source code of our integration tests.
  • The src/integration-test/resources directory must contain the resources of our integration tests.
  • The names of our integration test classes don’t have to start or end with a special marker string.

We can make the required changes to our Maven build by following these steps:

First, configure the Build Helper Maven plugin. We will use this plugin for adding new source and resource directories to our Maven build. We can configure this plugin by following these steps:

  1. Declare the Build Helper Maven plugin (version 3.5.0) in our POM file.
  2. Create an execution that adds the source directory of our integration tests (src/integration-test/java) to our Maven build. This execution must invoke the add-test-source goal of the Build Helper Maven plugin when the generate-test-sources phase of the Maven default lifecycle is invoked.
  3. Create an execution that adds the resource directory of our integration tests (src/integration-test/resources) to our Maven build. This execution must invoke the add-test-resource goal of the Build Helper Maven plugin when the generate-test-resources phase of the Maven default lifecycle is invoked.

After we have configured the Build Helper Maven plugiin, its configuration looks as follows:

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>build-helper-maven-plugin</artifactId>
	<version>3.5.0</version>
	<executions>
		<execution>
			<id>add-integration-test-sources</id>
			<phase>generate-test-sources</phase>
			<goals>
				<goal>add-test-source</goal>
			</goals>
			<configuration>
				<sources>
					<source>src/integration-test/java</source>
				</sources>
			</configuration>
		</execution>
		<execution>
			<id>add-integration-test-resources</id>
			<phase>generate-test-resources</phase>
			<goals>
				<goal>add-test-resource</goal>
			</goals>
			<configuration>
				<resources>
					<resource>
						<filtering>true</filtering>
						<directory>src/integration-test/resources</directory>
					</resource>
				</resources>
			</configuration>
		</execution>
	</executions>
</plugin>

Second, configure the Maven Surefire plugin to run only unit tests (aka tests which have the JUnit 5 tag: unitTest). After we have made the required changes to the configuration of the Maven Surefire plugin, it looks as follows:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-surefire-plugin</artifactId>
	<version>3.2.5</version>
	<configuration>
		<groups>unitTest</groups>
		<skipTests>${skip.unit.tests}</skipTests>
	</configuration>
</plugin>

Third, configure the Maven Failsafe plugin to run only integration tests (aka tests which have the JUnit 5 tag: integrationTest). After we have made the required changes to the configuration of the Maven Failsafe plugin, it looks as follows:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-failsafe-plugin</artifactId>
	<version>3.2.5</version>
	<configuration>
		<includes>
			<include>**/*.java</include>
		</includes>
		<groups>integrationTest</groups>
	</configuration>
	<executions>
		<execution>
			<id>integration-tests</id>
			<goals>
				<goal>integration-test</goal>
				<goal>verify</goal>
			</goals>
			<configuration>
				<skipTests>${skip.integration.tests}</skipTests>
			</configuration>
		</execution>
	</executions>
</plugin>

We have now made the required changes to our Maven build. Let's move on and find out how we can run our tests with Maven.

Running Our Tests With Maven

We can run our tests with Maven by running the following commands at command prompt:

First, if we want to run only unit tests, we have to use the command:

mvn clean test -P unit-tests

Second, if we want to run only integration tests, we have to use the command:

mvn clean verify -P integration-tests

Third, if we want to run both unit and integration tests, we have to use the command:

mvn clean verify -P all-tests

We can now create a Maven build that makes our life as easy as possible and we know how we can run our tests with Maven. Let's summarize what we learned from this blog post.

Summary

This blog post has taught us five things:

  • Because we want to keep our feedback loop as short as possible, must be able to select which tests are run.
  • If our build contains unit and integration tests, we must create Maven profiles which allow us to run only unit tests, only integration tests, and all tests.
  • If we don't want to waste any time wondering whether a test class contains unit or integration tests, we must put different tests to different directories.
  • We can add new source and resource directories to our Maven build by using the Build Helper Maven plugin.
  • We can select the invoked tests by leveraging Maven build profiles and configuring the Maven Surefire and Maven Failsafe plugins.

P.S. You can get the example applications from Github.

3 comments… add one
  • fantaman Feb 17, 2024 @ 21:50

    If one sticks with the name conventions for UnitTests (e.g. ending on "Test") and IntegrationTests (e.g. ending on "IT") as specified by the SureFire and FailSafe plugins (see their defaults), then this laborious setup with profiles and properties is not necessary: all that's needed is including both plugins in the build section of the pom.xml (as described on the respective usage.html).
    Then UnitTests will be executed with "mvn test" and IntegrationTests with "mvn verify", as is the maven default.

    • Petri Nov 4, 2024 @ 23:27

      You are correct. If you can live with the default naming convention, you shouldn't follow the advice given in this blog post. Thank you for pointing this out.

      Obviously, I don't like that naming convention, and since you have to do the configuration only once, I don't think that it's a problem.

      Also, I want to point out that a big part of the complexity is caused by the configuration of the Build Helper Maven plugin which you don't need if you can live with the fact that all your tests are in the src/test/java directory.

  • torin-st Apr 23, 2024 @ 10:30

    +1

Leave a Reply