Creating a strong foundation for a new project using Spring Boot – Part 3

Contents

This post is divided into four parts.

  1. Getting Ready
  2. Unit testing
  3. Integration testing
  4. Launching the app

Integration tests

In integration tests, multiple layers communicate with each other and we test multiple layers of the system in a given test. It’s a good idea to keep your integration tests separate from unit tests. You will run unit tests more often, with every build for example. The integration tests might run with a nightly build.

Create two new source folders src/integrationTest/java and src/integrationTest/resources in your IDE. In eclipse, Right click on project>New>Source folder.

Create a new file application-integrationtest.properties in  src/integrationTest/resources.

[code language=”plain”]

spring.datasource.url = jdbc:h2:mem:test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

[/code]

Here, we have configured H2 in-memory database that will be used for our integration testing. Having H2 will remove the need to have a DB installed on the system on which the tests are run. Hence, this will give you more flexibility in running the tests.

UserRestControllerIntTest.java

[code language=”java”]

package org.websandbox.learning.springdata.user;

//imports omitted

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = BasicLayoutApplication.class)
@AutoConfigureMockMvc
@TestPropertySource(locations = "classpath:application-integrationtest.properties")
public class UserRestControllerIntTest {

@Autowired
MockMvc mvc;

@Autowired
UserRepository repository;

@After
public void resetDb() {
repository.deleteAll();
}

@Test
public void givenValidInputItShouldCreateUser() throws IOException, Exception {
User bob = new User("bob");
mvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON).content(JsonUtil.toJson(bob)));

List<User> usersFound = repository.findAll();
assertThat(usersFound).extracting(User::getName).containsOnly("bob");
}

@Test
public void shouldListAllUsers() throws Exception {

createUser("bob");
createUser("alex");

mvc.perform(get("/api/users").contentType(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$", hasSize(greaterThanOrEqualTo(2)))).andExpect(jsonPath("$[0].name", is("bob")))
.andExpect(jsonPath("$[1].name", is("alex")));
}

private void createUser(String name) {
User user = new User(name);
repository.saveAndFlush(user);
}

}

[/code]

Spring Boot provides a @SpringBootTest annotation which can be used as an alternative the standard spring-test @ContextConfiguration annotation when you need Spring Boot features. The annotation works by creating the ApplicationContext used in your tests via SpringApplication.

You can use the webEnvironment attribute of @SpringBootTest to further refine how your tests will run:

  • MOCK — Loads a WebApplicationContext and provides a mock servlet environment. Embedded servlet containers are not started when using this annotation. If servlet APIs are not on your classpath this mode will transparently fallback to creating a regular non-web ApplicationContext.
  • RANDOM_PORT — Loads anEmbeddedWebApplicationContext and provides a real servlet environment. Embedded servlet containers are started and listening on a random port.
  • DEFINED_PORT — Loads anEmbeddedWebApplicationContext and provides a real servlet environment. Embedded servlet containers are started and listening on a defined port (i.e from your application.properties or on the default port 8080).
  • NONE — Loads an ApplicationContext using SpringApplication but does not provide any servlet environment (mock or otherwise).

@AutoConfigureMockMvc provides auto-configuration of MockMvc. Mock MVC offers a powerful way to quickly test MVC controllers without needing to start a full HTTP server.

@TestPropertySource is a class-level annotation that is used to configure the locations of properties files and inlined properties to be added to the set ofPropertySources in the Environment for an ApplicationContext loaded for an integration test. Note that the property file loaded with @TestPropertySource will override the existing application.properties file.

Configuring Gradle for integration tests

At this point, we do not have any way to execute the integration tests with Gradle. One main reason for this is that Gradle does not know about the new source directories that we recently created for integration tests. Let’s fix that first.

Open your build.gradle file and add the following code.

[code language=”plain”]
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}

sourceSets {
integrationTest {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
}
}
[/code]

The first block will inherit all the dependencies from compile time and run time dependencies. The second block configures a sourceSet. A sourceSet class represents a logical group of Java source and resources. Naturally, they also have to inherit from the test and main classes.

Once this is done, we need a way to launch the integration tests. This can be achieved by creating a separate task.

[code language=”plain”]
task integrationTest(type: Test) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
reports.html.destination = file("${reporting.baseDir}/integrationTest")
}
[/code]

Note that we have created a new gradle task called integrationTest. We have also specified the location where the reports will be generated. By default, they will be generated in build/reports/tests. If we do
not override them and launch both tests and integration tests with gradle clean test integrationTest, they will override each other.

Launch the integration tests

In order to launch the integration tests, just navigate to the project directory and execute the following command.

[code language=”plain”]
gradlew integrationTest
[/code]

All of your integration tests should pass and the reports should be generated in build/reports/tests directory.

Checkpoint

integration-tests

Continue reading Part 4.