Unit Testing Spring Async Rest Controller with MockMvc

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 !!

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode