Summary 1.
Software testing is an application software quality assurance. Java developers often neglect interface unit testing when developing interfaces. Mock unit testing as a Java developer will significantly reduce your bug count. Spring provides the Test test module, so now let’s play with the Mock unit test under SpringBoot. We will test the unit test of controller and Service.
2. Dependency introduction
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Copy the code
Import dependencies as above and scope is test. This dependency provides the following class libraries
- JUnit 4: The most powerful Unit testing framework for Java applications today
- Spring Test & Spring Boot Test: Spring Boot integration Test support.
- AssertJ: A Java assertion library that provides test assertion support.
- Hamcrest: Object matches assertion and constraint components.
- Mockito: Well-known Java Mock simulation framework.
- JSONassert: JSON assertion library.
- JsonPath: JSON XPath manipulation library.
All of these are common libraries that are used in unit testing. You’d better study it sometime.
3. Configure the test environment
A Spring Boot application is a Spring ApplicationContext, and normal tests do not go beyond that. The testing framework provides an @SpringBoottest annotation to provide SpringBoot unit test environment support. If you are using JUnit 4 don’t forget to add @runwith (springrunner.class) to the test class, JUnit 5 won’t need it. By default, @Springboottest does not start the server. You can use the webEnvironment property to further optimize how the test is run.
MOCK
(Default) : Loads the Web ApplicationContext and provides a mock Web environment. The embedded server will not be started under this option. If there is no Web environment on the classpath, a regular non-Web is createdApplicationContext
. You can play along@AutoConfigureMockMvc
or@AutoConfigureWebTestClient
A simulated Web application.RANDOM_PORT
Loading:WebServerApplicationContext
And provide a real Web environment with random Web container ports enabled.DEFINED_PORT
Loading:WebServerApplicationContext
And provide a real Web environment andRANDOM_PORT
The difference is to enable the SpringBoot application port that you have activated, which is usually declared inapplication.yml
Configuration file.NONE
Through:SpringApplication
Loads aApplicationContext
. But no Web environment (Mock or otherwise) is provided.
Note: If your test has the @Transactional annotation, by default the transaction is rolled back after each test method executes. But when your webEnvironment is set to RANDOM_PORT or DEFINED_PORT, implicitly providing a real servlet Web environment, there is no rollback. This is especially important to ensure that you do not write dirty data during production release tests.
4. Write test classes to test your API
Without further ado, first we write a BookService as a Service layer
package cn.felord.mockspringboot.service;
import cn.felord.mockspringboot.entity.Book;
/**
* The interface Book service.
*
* @author Dax
* @since14:54 * / 2019-07-23
public interface BookService {
/**
* Query by title book.
*
* @param title the title
* @return the book
*/
Book queryByTitle(String title);
}
Copy the code
The implementation class is as follows. For simplicity, there is no test persistence layer. If the persistence layer needs to be tested, add, delete, or change it requires Spring transaction annotation @Transactional support to rollback after testing.
package cn.felord.mockspringboot.service.impl;
import cn.felord.mockspringboot.entity.Book;
import cn.felord.mockspringboot.service.BookService;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
/ * * *@author Dax
* @since14:55 2019-07-23 * /
@Service
public class BookServiceImpl implements BookService {
@Override
public Book queryByTitle(String title) {
Book book = new Book();
book.setAuthor("dax");
book.setPrice(78.56);
book.setReleaseTime(LocalDate.of(2018.3.22));
book.setTitle(title);
returnbook; }}Copy the code
The controller layer is as follows:
package cn.felord.mockspringboot.api;
import cn.felord.mockspringboot.entity.Book;
import cn.felord.mockspringboot.service.BookService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/ * * *@author Dax
* @sinceAnd the 2019-07-23 * /
@RestController
@RequestMapping("/book")
public class BookApi {
@Resource
private BookService bookService;
@GetMapping("/get")
public Book getBook(String title) {
returnbookService.queryByTitle(title); }}Copy the code
We wrote our own test classes in the corresponding classpath under the unit test package test of the Spring Boot Maven project
package cn.felord.mockspringboot;
import cn.felord.mockspringboot.entity.Book;
import cn.felord.mockspringboot.service.BookService;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
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.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import javax.annotation.Resource;
import java.time.LocalDate;
/** * The type Mock springboot application tests. */
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockSpringbootApplicationTests {
@Resource
private MockMvc mockMvc;
@MockBean
private BookService bookService;
@Test
public void bookApiTest(a) throws Exception {
String title = "java learning";
// MockBean starts the emulation
bookServiceMockBean(title);
// MockBean completes the simulation
String expect = "{\" title \ ": \" Java learning \ ", \ "the author \" : \ "dax \", \ "price \" : 78.56, \ "releaseTime \" : \ "2018-03-22 \"}";
mockMvc.perform(MockMvcRequestBuilders.get("/book/get")
.param("title", title))
.andExpect(MockMvcResultMatchers.content()
.json(expect))
.andDo(MockMvcResultHandlers.print());
/ / mockbean reset
}
@Test
public void bookServiceTest(a) {
String title = "java learning";
bookServiceMockBean(title);
Assertions.assertThat(bookService.queryByTitle("ss").getTitle()).isEqualTo(title);
}
/** * Mock *@param title the title
*/
private void bookServiceMockBean(String title) {
Book book = new Book();
book.setAuthor("dax");
book.setPrice(78.56);
book.setReleaseTime(LocalDate.of(2018.3.22)); book.setTitle(title); BDDMockito.given(bookService.queryByTitle(title)).willReturn(book); }}Copy the code
The first two test class annotations go without saying, but the third annotation @autoConfiguRemockMVC may be unfamiliar to you. This is used to enable automated configuration of Mock Mvc tests.
We then write a test method, bookApiTest(), to test the BookApi#getBook(String title) interface.
The logic is that MockMvc executes a mock GET request and expects the result to be an Expect Json string and prints out the corresponding object (identified in Figure 1 below). Java.lang.AssertionError will be raised if the request is rejected, and the Expected will be printed with the actual value (identified in Figure 2 below). If it is as expected, only figure 1 below appears.
5. Test piling
There is a very common situation in the development, you may call other services are not developed, for example, you have an SMS send interface is still processing SMS interface, but you still need SMS interface to test. You can build an implementation of the abstract interface with @MockBean. In the case of the BookService above, if the implementation class logic is not determined, we can simulate the logic of the bean by specifying its input parameter and the corresponding return value, or by selecting A routing operation based on the case (B if the input parameter is A, D if the input parameter is C). This simulation is also known as test piling. Here we will use Mockito
The test scenario is as follows:
- Specifies the return value of the piling object
- Determines how many times a method of a piling object is called
- Specifies that the piling object throws a specific exception
There are generally the following combinations:
- do/whenIncluding:
DoThrow (...). When (...).
/DoReturn (...). When (...).
/DoAnswer (...). When (...).
- given/willIncluding:
Given (...). WillReturn (...).
/Given (...). WillAnswer (...).
- when/thenIncluding:
When (...). ThenReturn (...).
/When (...). ThenAnswer (...).
If the input parameter is A, the result is B; if the input parameter is C, the result is D. So let’s do it, basically the same way we started, but with @MockBean
Then use Mockito write the pile method void bookServiceMockBean(String Title) to simulate the above BookServiceImpl implementation class. However, the simulated bean is automatically reset after each test. And cannot be used to simulate the behavior of beans running during an application context refresh.
Then inject this method into the Controller test method to test it.
6. Other
The built-in AssertJ is also a common assertion, and the API is very friendly, which is also briefly demonstrated here with bookServiceTest()
7. To summarize
This article implemented some simple Spring Boot enabled integration tests. The construction of the test environment, test code preparation of the actual combat operation, basic can meet the needs of daily development and testing, I believe you can learn a lot from this article.
The tutorial code is available from Gitee.
You can also get more dry goods to share through my personal blog.
Follow our public id: Felordcn for more information
Personal blog: https://felord.cn