Unit Testing Spring Boot with JUnit5




Testing Spring Boot App with JUnit 5 and Mockito

Writing Unit Tests for an application is very important to make sure the software works as expected. JUnit 5 is the latest release of Junit 5 and it is already gaining popularity. In this article, we will walk through a simple Spring Boot Application and will write unit tests using Junit and Mockito.

Purpose

Before we dig into would like to give a broad overview of JUnit 5 and Mockito. Junit5 is the latest release of Junit, it is much different then Junit4, so many of the Junit4 specific annotations do not work with Junit 5. Mockito is a mocking framework and is very popular in the Opensource community. With Mockito we can mock an object or a method. For example in this article, we will be writing unit testing for the controller and we will mock the service layer.

Application Details

The Spring boot application we are building here is a simple App with one controller and one service class. The application creates rest endpoints (/blogs, /blogs/ID) through which we can get the list of blogs and specific blog details.

Code Structure

Spring Boot JUNIT5 Project Structure


Code details

pom.xml
<?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>
 <artifactId>com.bootng.springboot-rest</artifactId>
 <version>1.0.0-SNAPSHOT</version>
 <packaging>war</packaging>
 <name>bootngSpringboot  Rest</name>
 <description>bootng Springboot Rest</description>
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.2.6.RELEASE</version>
  <relativePath />
 </parent>
 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
 </properties>
 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <scope>test</scope>
  </dependency>
  <!-- junit 5 -->
  <dependency>
   <groupId>org.junit.jupiter</groupId>
   <artifactId>junit-jupiter-engine</artifactId>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-core</artifactId>
   <version>2.19.0</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.junit.jupiter</groupId>
   <artifactId>junit-jupiter-api</artifactId>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
     <addResources>true</addResources>
    </configuration>
    <executions>
     <execution>
      <goals>
       <goal>repackage</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
  </plugins>
 </build>
</project>
RestApplication.java
Our main application class.
package com.bootng;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ComponentScan({"com.bootng"})
@SpringBootApplication
public class RestApplication {
  private static final Logger log = LoggerFactory.getLogger(RestApplication.class);
  public static void main(String args[]) {
    log.info("about to call RestApplication.run()");
    SpringApplication.run(RestApplication.class, args);
    log.info("completed executing RestApplication.run()");
  }
}
BlogService.java
Our service class which has three methods getBlogStory(String id), getBlogStory() and getBlogTags() respectively. Controller class which exposes the REST endpoints will call these methods.
@Service
public class BlogService {
  List category = Arrays.asList("Technical", "Travel", "Food", "Finance", "Entertainment");
  List stories = new ArrayList();
  {
    stories.add(new BlogStory("Java 11", "Technical", "Java 11 Blog"));
    stories.add(new BlogStory("Java 14", "Technical", "Java 14 Blog"));
    stories.add(new BlogStory("Asia Travel", "Travel", "Places to visit in Asia"));
    stories.add(new BlogStory("Europe Travel", "Travel", "Places to visit in Europe"));
    stories.add(new BlogStory("Japan Travel", "Travel", "Places to visit in Japan"));
    stories.add(new BlogStory("Asian Food", "Food", "Asian Food......"));
  }

  public BlogStory getBlogStory(String id) throws AppException{
    return stories.stream().filter(story -> id.equals(story.getId())).findAny().orElse(null);
  }
  public List getBlogStory() throws AppException {
    return stories;
  }
  public List getBlogTags() throws AppException{
    return category;
  }
}
BlogAPIController.java
Controller class which creates two rest endpoints /blogs and /blog/ID. Both methods might return an error responses.
@Controller
@RequestMapping("/blogapi")
public class BlogAPIController {
  private static final Logger log = LoggerFactory.getLogger(BlogAPIController.class);
  @Autowired
  BlogService blogService;

  @RequestMapping(value = {"/blogs"}, method = RequestMethod.GET,
      produces = MediaType.APPLICATION_JSON_VALUE)
  public @ResponseBody ResponseEntity> getBlogStories() {
    log.info("inside getBlogStories GET method");
    List blogStory = null;
    ResponseEntity> apiResponse;
    try {
      blogStory = blogService.getBlogStory();
      apiResponse = new ResponseEntity>(blogStory, HttpStatus.OK);
    } catch (AppException e) {
      apiResponse = new ResponseEntity>(blogStory, HttpStatus.INTERNAL_SERVER_ERROR);
      e.printStackTrace();
    }
    return apiResponse;
  }
  
  @RequestMapping(value = {"/blog"}, method = RequestMethod.GET,
      produces = MediaType.APPLICATION_JSON_VALUE)
  public @ResponseBody ResponseEntity  getBlogStory(@PathParam(value = "") String id) {
    log.info("inside blog GET method");
    BlogStory blogStory = null;
    ResponseEntity  apiResponse;
    try {
      blogStory = blogService.getBlogStory(id);
      if (blogStory == null) 
        apiResponse = new ResponseEntity(blogStory, HttpStatus.NOT_FOUND);
      else
        apiResponse = new ResponseEntity(blogStory, HttpStatus.OK);
    } catch (AppException e) {
      apiResponse = new ResponseEntity(blogStory, HttpStatus.INTERNAL_SERVER_ERROR);
      e.printStackTrace();
    }
     return apiResponse;
  }
}
BlogServiceTest.java
In this Test class, we are testing the two methods of the Service class. We get an autowired instance of the BlogService class. We use the @ExtendWith annotation of JUnit5 . We also use the @ContextConfiguration annotation from Spring Boot to load the appropriate context.
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {BlogService.class})
public class BlogServiceTest {

  @Autowired
  BlogService service;

  @Test
  public void test_getBlogStory() {
    List list;
    try {
      list = service.getBlogStory();
      Assertions.assertNotNull(list, "list should not be null");
    } catch (AppException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  @Test
  public void test_getBlogStory_with_id() {
    BlogStory data;
    try {
      data = service.getBlogStory("Java_11");
      Assertions.assertNotNull(data, "data should not be null");
    } catch (AppException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {BlogAPIController.class, BlogService.class})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class BlogAPIControllerTest {
  
  @Mock
  BlogService blogService;
  
  @InjectMocks
  private BlogAPIController controller;
  
  @BeforeAll
  public void setup() {
      MockitoAnnotations.initMocks(this);
  }
  
  @Test
  public void contextLoads() {}
  
  @Test
  public void test_getBlogStories() throws AppException {
    
    when(blogService.getBlogStory()).thenReturn(new ArrayList());
    
    ResponseEntity> object = controller.getBlogStories();
    
    Assertions.assertEquals(HttpStatus.OK, object.getStatusCode(), "OK Status");
    
  }
  
  @Test
  public void test_getBlogStory_not_found() throws AppException {
    
    when(blogService.getBlogStory("444")).thenReturn(null);
    
    ResponseEntity object = controller.getBlogStory("444");
    
    Assertions.assertEquals(HttpStatus.NOT_FOUND, object.getStatusCode(), "OK Status");
    
  }

}
BlogAPIControllerTest
Our controller test class. In this class we use Mokito to inject a mocked BlogService, then we can use the stubbing method like "when" to return different results from the service class.
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {BlogAPIController.class, BlogService.class})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class BlogAPIControllerTest {
  
  @Mock
  BlogService blogService;
  
  @InjectMocks
  private BlogAPIController controller;
  
  @BeforeAll
  public void setup() {
      MockitoAnnotations.initMocks(this);
  }
 
  @Test
  public void contextLoads() {}
  
  @Test
  public void test_getBlogStories_success() throws AppException {
    
    when(blogService.getBlogStory()).thenReturn(new ArrayList());
    
    ResponseEntity> object = controller.getBlogStories();
    
    Assertions.assertEquals(HttpStatus.OK, object.getStatusCode(), "OK Status");
 }
  
  @Test
  public void test_getBlogStory_Not_found() throws AppException {
    
    when(blogService.getBlogStory("444")).thenReturn(null);
    
    ResponseEntity object = controller.getBlogStory("444");
    
    Assertions.assertEquals(HttpStatus.NOT_FOUND, object.getStatusCode(), "OK Status");
  }

}

Summary

What is covered
In this article, we saw how to write a simple Spring Boot Application. Write Unit tests and execute unit tests. Mock service classes.
Source code and Build
git clone https://github.com/bootng/spring-boot-references cd springboot-junit #build the application mvn install #run the tests mvn test

No comments :

Post a Comment

Please leave your message queries or suggetions.