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