Spring boot MockMvc – Unit test async rest controller

Learn to test spring boot async rest controller using MockMVC which support async request processing and return the response asynchronously.

1. spring boot async controller

Given is a async controller which returns a simple string output after the delay of 5 seconds. We will write the unit test for this controller.

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;

import org.springframework.core.task.TaskExecutor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldCompletableFutureController 
{
      private final TaskExecutor taskExecutor;

      public HelloWorldCompletableFutureController(TaskExecutor taskExecutor) 
      {
          this.taskExecutor = taskExecutor;
      }

      @GetMapping(value = "/testCompletableFuture")
      public CompletableFuture<String> echoHelloWorld2() 
      {
            return CompletableFuture.supplyAsync(() -> 
            {
                  randomDelay();
                  return "Hello World !!";
            }, taskExecutor);
      }

      private void randomDelay() 
      {
            try 
            {
                  Thread.sleep(ThreadLocalRandom.current().nextInt(5000));
            } 
            catch (InterruptedException e) 
            {
                  Thread.currentThread().interrupt();
            }
      }
}

2. How to test async rest controller with MockMvc

Just as with regular controllers, the Spring WebMVC Test framework can be used to test async controllers.

Create a test class and annotate it with @WebMvcTest and @RunWith(SpringRunner.class). The @WebMvcTest will bootstrap a minimal Spring Boot application containing the things needed to test the controller. It automatically configures Spring MockMvc, which is autowired into the test.

Please note that to test a async controller, the async dispatching needs to be initiated using asyncDispatch() method.

Below given test perform following things:

  • The initial request is performed and validated for async feature to be started.
  • The asyncDispatch() is applied and request is sent.
  • Finally, we assert the expected response.
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
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.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import com.howtodoinjava.springasyncexample.web.controller.HelloWorldCompletableFutureController;

@RunWith(SpringRunner.class)
@WebMvcTest(HelloWorldCompletableFutureController.class)
public class HelloWorldCompletableFutureControllerTest 
{

      @Autowired
      private MockMvc mockMvc;

      @Test
      public void testHelloWorldController() throws Exception 
      {
            MvcResult mvcResult = mockMvc.perform(get("/testCompletableFuture"))
                              .andExpect(request().asyncStarted())
                              .andDo(MockMvcResultHandlers.log())
                              .andReturn();
            
            mockMvc.perform(asyncDispatch(mvcResult))
                        .andExpect(status().isOk())
                        .andExpect(content().contentTypeCompatibleWith("text/plain"))
                        .andExpect(content().string("Hello World !!"));
      }
}

3. Sourcecode files

The application class which bootstrap the application and configure the async behavior. To override the default async behavior such as thread pool and timeout, you can implement the WebMvcConfigurer interface and override it’s configureAsyncSupport() method.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class SpringAsyncExampleApplication implements WebMvcConfigurer {

      public static void main(String[] args) {
            SpringApplication.run(SpringAsyncExampleApplication.class, args);
      }

      @Override
      public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
            configurer.setTaskExecutor(mvcTaskExecutor());
            configurer.setDefaultTimeout(30_000);
      }

      @Bean
      public ThreadPoolTaskExecutor mvcTaskExecutor() {
            ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
            taskExecutor.setThreadNamePrefix("mvc-task-");
            return taskExecutor;
      }
}

3.1. SpringAsyncExampleApplication.java

3.2. application.properties

Enable debug logging here to understand the behavior of the application.

logging.level.org.springframework=DEBUG
logging.level.com.howtodoinjava=DEBUG

3.3. pom.xml

The used pom.xml is:

<?xml version="1.0" encoding="UTF-8"?>
<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>
      
      <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.2.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
      </parent>
      
      <groupId>com.howtodoinjava.demo</groupId>
      <artifactId>spring-async-demo</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <name>spring-async-demo</name>
      <description>Demo project for Spring Boot</description>

      <properties>
            <java.version>1.8</java.version>
      </properties>

      <dependencies>
      
            <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            
            <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-webflux</artifactId>
            </dependency>

            <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
                  <scope>test</scope>
            </dependency>
            <dependency>
                  <groupId>io.projectreactor</groupId>
                  <artifactId>reactor-test</artifactId>
                  <scope>test</scope>
            </dependency>
      </dependencies>

      <build>
            <plugins>
                  <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
            </plugins>
      </build>
      
      <repositories>
        <repository>
            <id>repository.spring.release</id>
            <name>Spring GA Repository</name>
            <url>http://repo.spring.io/release</url>
        </repository>
    </repositories>

</project>

Let me know if you face any error while testing the asynchronous spring boot rest controller.

Happy Learning !!

Was this post helpful?

Join 7000+ Fellow Programmers

Subscribe to get new post notifications, industry updates, best practices, and much more. Directly into your inbox, for free.

Leave a Comment

HowToDoInJava

A blog about Java and its related technologies, the best practices, algorithms, interview questions, scripting languages, and Python.