JUnit 5 extension for Spring

In order to integrate the spring-test capabilities into JUnit 5's Jupiter programming model, SpringExtension has been developed. This extension is part of the spring-test module, as of Spring 5. Let's see several examples of JUnit 5 and Spring 5 together.

Let’s suppose we want to make an integration in-container test of the Spring application described in the former section, made up of three classes: MySpringApplication, MessageComponent, and MessageService. As we have learned, in order to implement a Jupiter test against this application, we need to make the following steps:

  1. Annotate our test class with @ContextConfiguration to specify which ApplicationContext needs to be loaded.
  2. Annotate our test class with @ExtendWith(SpringExtension.class) to enable spring-test into Jupiter.
  3. Inject the Spring component we want to assess in our test class.
  4. Implement our test (@Test).

For example:

package io.github.bonigarcia;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { MySpringApplication.class })
class SimpleSpringTest {

@Autowired
public MessageComponent messageComponent;

@Test
public void test() {
assertEquals("Hello world!", messageComponent.getMessage());
}

}

This is a very simple example in which the Spring component called MessageComponent is assessed. When this test is started, our ApplicationContext is initiated with and all our Spring components inside. After that, in this example, the bean MessageComponent is injected in the test, which is assessed simply calling the method getMessage() and verifying its response.

It is worth to review which dependencies are needed for this test. When using Maven, these dependencies are the following:

    <dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

On the other side, if we use Gradle, the dependencies clause would be as follows:

dependencies {
compile("org.springframework:spring-context:${springVersion}")
testCompile("org.springframework:spring-test:${springVersion}")
testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}

Note that in both cases the spring-context dependency is needed to implement the application, and then we need spring-test and junit-jupiter to test it. In order to implement the equivalent application and test, but this time using Spring Boot, first we would need to change our pom.xml (when using Maven):

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.bonigarcia</groupId>
<artifactId>junit5-spring-boot</artifactId>
<version>1.0.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M3</version>
</parent>

<properties>
<junit.jupiter.version>5.0.0</junit.jupiter.version>
<junit.platform.version>1.0.0</junit.platform.version>
<java.version>1.8</java.version>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.compiler.source>${java.version}</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-milestones</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>

</project>

Or our build.gradle (when using Gradle):

buildscript {
ext {
springBootVersion = '2.0.0.M3'
junitPlatformVersion = '1.0.0'
}

repositories {
mavenCentral()
maven {
url 'https://repo.spring.io/milestone'
}
}

dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.junit.platform:junit-platform-gradle-plugin:${junitPlatformVersion}")
}
}

repositories {
mavenCentral()
maven {
url 'https://repo.spring.io/libs-milestone'
}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.junit.platform.gradle.plugin'

jar {
baseName = 'junit5-spring-boot'
version = '1.0.0'
}

compileTestJava {
sourceCompatibility = 1.8
targetCompatibility = 1.8
options.compilerArgs += '-parameters'
}

dependencies {
compile('org.springframework.boot:spring-boot-starter')
testCompile("org.springframework.boot:spring-boot-starter-test")
testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}

In order to transform our raw Spring application into Spring Boot, our components (in the example called MessageComponent and MessageService) would be exactly the same, but our main class would change a bit (see here). Notice that we use the annotation @SpringBootApplication at class level, implementing the main method with the typically bootstrapping mechanism of Spring Boot. Just for logging purposes, we are implementing a method annotated with @PostConstruct. This method will be triggered just before the application context is started:

package io.github.bonigarcia;

import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MySpringBootApplication {
final Logger log = LoggerFactory.getLogger(MySpringBootApplication.class);

@Autowired
public MessageComponent messageComponent;

@PostConstruct
private void setup() {
log.info("*** {} ***", messageComponent.getMessage());
}

public static void main(String[] args) throws Exception {
new SpringApplication(MySpringBootApplication.class).run(args);
}

}

The implementation of the test would be straightforward. The only change we need to do is to annotate the test with @SpringBootTest instead of @ContextConfiguration (Spring Boot automatically looks for and starts our ApplicationContext):

package io.github.bonigarcia;

import static org.junit.jupiter.api.Assertions.assertEquals;

import
org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@SpringBootTest
class SimpleSpringBootTest {

@Autowired
public MessageComponent messagePrinter;

@Test
public void test() {
assertEquals("Hello world!", messagePrinter.getMessage());
}

}

Executing the test in the console, we can see that actually the application is started before the test (notice the unmistakable spring ASCII banner at the beginning).

After that, our test uses the ApplicationContext to verify one Spring component, and as a result the test is succeeded:

Execution of test using Spring Boot

To finish with this part, we see a simple web application implemented with Spring Boot. With respect to the dependencies, the only change we need to do is to include the started spring-boot-starter-web (instead of the generic spring-boot-starter). That’s it, we can start implementing our Spring-based web application.

We are going to implement a very simple @Controller, that is, the Spring bean, which handles the request from the browsers. In our example, the only URL mapped by the controller is the default resource /:

package io.github.bonigarcia;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class WebController {

@Autowired
private PageService pageService;

@RequestMapping(value = "/", method = GET)
public String greeting() {
return pageService.getPage();
}

}

This component injects a service called PageService, responsible of returning the actual page to be loaded in response to the request to /. The content of this service is also very simple:

package io.github.bonigarcia;

import org.springframework.stereotype.Service;

@Service
public class PageService {

public String getPage() {
return "/index.html";
}

}

By convention (we are using Spring Boot here), the static resource for Spring-based web applications are located in a folder called static within the project classpath. Following the structure of Maven/Gradle project, this folder is located in the src/main/resources path (see screenshot below). Note that there are two pages there (we switch from one to the other in the tests, stay tuned):

Content of the example project junit5-spring-boot-web

Let’s move on not the interesting part: the tests. We are implementing three Jupiter tests in this project. The first one is devoted to verify a direct call to the page /index.html. As depicted before, this test needs to use the Spring extension (@ExtendWith(SpringExtension.class)) and be declared as Spring Boot test (@SpringBootTest). To carry out the request to web application, we use an instance of the MockMvc, verifying the response in several ways (HTTP response code, content-type, and response content body). This instance is automatically configured using the Spring Boot annotation @AutoConfigureMockMvc.

Out of Spring Boot, instead of using @AutoConfigureMockMvc, the object MockMvc can be created using a builder class called MockMvcBuilders. In this case, the application context is used as parameter for that builder.
package io.github.bonigarcia;

import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class IndexTest {

@Autowired
MockMvc mockMvc;

@Test
void testIndex() throws Exception {
mockMvc.perform(get("/index.html")).andExpect(status().isOk())
.andExpect(content().contentType("text/html")).andExpect(
content().string(containsString("This is index
page")));
}

}

Again, running this test in the shell, we check that the application is actually executed. By default, the embedded Tomcat listens the port 8080. After that, test is executed successfully:

Console output of in-container first test

Second test is similar, but as a differential factor it uses the test capability @MockBean to override a spring component (in this example, PageService) by a mock. In the body of the test, first we stub the method getPage of the mock to change the default response of the component to redirect:/page.html. As a result, when requesting the resource / in the test with the object MockMvc, we will obtain an HTTP 302 response (redirect) to the resource /page.html (which is actually an existing page, as shown in the project screenshot):

package io.github.bonigarcia;

import static org.mockito.Mockito.doReturn;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class RedirectTest {

@MockBean
PageService pageService;

@Autowired
MockMvc mockMvc;

@Test
void test() throws Exception {
doReturn("redirect:/page.html").when(pageService).getPage();
mockMvc.perform(get("/")).andExpect(status().isFound())
.andExpect(redirectedUrl("/page.html"));
}

}

Similarly, in the shell we can confirm that the test starts the Spring application and then it is executed correctly:

Console output of in-container second test

The last test in this project is an example of an out-of-container test. In the previous test examples, the Spring context was used within the test. On the other side, the following relies completely in Mockito to exercise the components of the system, this time without starting the Spring application context. Note that we are using the MockitoExtension extension here, using the component WebController as our SUT (@InjectMocks) and the component PageService as DOC (@Mock):

package io.github.bonigarcia;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import io.github.bonigarcia.mockito.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class OutOfContainerTest {

@InjectMocks
private WebController webController;

@Mock
private PageService pageService;

@Test
void test() {
when(pageService.getPage()).thenReturn("/my-page.html");
assertEquals("/my-page.html", webController.greeting());
verify(pageService, times(1)).getPage();
}

}

This time, in the execution of the test, we do not see spring traces since the application container was not started before executing the test:

Console output of out-of-container test
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.132.99