Spring 框架提供了一個專門的測試模塊(spring-test),用於應用程序的單元測試。 在 Spring Boot 中,你可以通過spring-boot-starter-test啟動器快速開啟和使用它。
在pom.xml文件中引入maven依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
1. JUnit單元測試
當你的單元測試代碼不需要用到 Spring Boot 功能,而只是一個簡單的測試時,你可以直接編寫你的 Junit 測試代碼:
public class SimpleJunitTest {
@Test
public void testSayHi() {
System.out.println("Hi Junit.");
}
}
2. Spring Boot單元測試
當你的集成測試代碼需要用到 Spring Boot 功能時,你可以使用@SpringBootTest注解。該注解是普通的 Spring 項目(非 Spring Boot 項目)中編寫集成測試代碼所使用的@ContextConfiguration注解的替代品。其作用是用於確定如何裝載 Spring 應用程序的上下文資源。
@RunWith(SpringRunner.class)
@SpringBootTest
public class BeanInjectTest {
@Autowired
private HelloService helloService;
@Test
public void testSayHi() {
System.out.println(helloService.sayHi());
}
}
@Service
public class HelloService {
public String sayHi() {
return "--- Hi ---";
}
public String sayHello() {
return "--- Hello ---";
}
}
當運行 Spring Boot 應用程序測試時,它會自動的從當前測試類所在的包起一層一層向上搜索,直到找到一個@SpringBootApplication或@SpringBootConfiguration注釋類為止。以此來確定如何裝載 Spring 應用程序的上下文資源。
主配置啟動類的代碼為:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
如果搜索算法搜索不到你項目的主配置文件,將報出異常:
java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=…) with your test
解決辦法是,按 Spring Boot 的約定重新組織你的代碼結構,或者手工指定你要裝載的主配置文件:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {YourApplication.class})
public class BeanInjectTest {
// ...
}
基於 Spring 環境的 Junit 集成測試還需要使用@RunWith(SpringJUnit4ClassRunner.class)注解,該注解能夠改變 Junit 並讓其運行在 Spring 的測試環境,以得到 Spring 測試環境的上下文支持。否則,在 Junit 測試中,Bean 的自動裝配等注解將不起作用。但由於 SpringJUnit4ClassRunner 不方便記憶,Spring 4.3 起提供了一個等同於 SpringJUnit4ClassRunner 的類 SpringRunner,因此可以簡寫成:@RunWith(SpringRunner.class)。
3. Spring MVC 單元測試
當你想對 Spring MVC 控制器編寫單元測試代碼時,可以使用@WebMvcTest注解。它提供了自配置的 MockMvc,可以不需要完整啟動 HTTP 服務器就可以快速測試 MVC 控制器。
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@WebMvcTest(HelloController.class)
public class HelloControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void testHello() throws Exception {
mvc.perform(get("/hello"))
.andExpect(status().isOk())
.andDo(print());
}
}
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello(ModelMap model) {
model.put("message", "Hello Page");
return "hello";
}
}
使用@WebMvcTest注解時,只有一部分的 Bean 能夠被掃描得到,它們分別是:
@Controller
@ControllerAdvice
@JsonComponent
Filter
WebMvcConfigurer
HandlerMethodArgumentResolver
其他常規的@Component(包括@Service、@Repository等)Bean 則不會被加載到 Spring 測試環境上下文中。
如果測試的 MVC 控制器中需要@ComponentBean 的參與,你可以使用@MockBean注解來協助完成:
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@WebMvcTest(HelloController.class)
public class HelloControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private HelloService helloService;
@Test
public void testSayHi() throws Exception {
// 模擬 HelloService.sayHi() 調用, 返回 "=== Hi ==="
when(helloService.sayHi()).thenReturn("=== Hi ===");
mvc.perform(get("/hello/sayHi"))
.andExpect(status().isOk())
.andDo(print());
}
}
@Controller
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/hello/sayHi")
public String sayHi(ModelMap model) {
model.put("message", helloService.sayHi());
return "hello";
}
}
Controller的mock單元測試的另一種方法:
package net.mingsoft.mdiy.action; import net.mingsoft.MSApplication; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebAppConfiguration @RunWith(SpringRunner.class) //使用隨機端口 @SpringBootTest(classes = {MSApplication.class})// 指定啟動類 public class ArticleActionMockTest { @Autowired private WebApplicationContext wac; private MockMvc mvc; @Before public void setUp() { mvc = MockMvcBuilders.webAppContextSetup(wac).build(); } @Test public void testGetMdiyForm() throws Exception{ this.mvc.perform(get("/ms/mdiy/contentModel/form")).andExpect(status().isOk()) .andDo(MockMvcResultHandlers.print()); } }
4. Spring Boot Web 單元測試
當你想啟動一個完整的 HTTP 服務器對 Spring Boot 的 Web 應用編寫測試代碼時,可以使用@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)注解開啟一個隨機的可用端口。Spring Boot 針對 REST 調用的測試提供了一個 TestRestTemplate 模板,它可以解析鏈接服務器的相對地址。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testSayHello() {
Map result = restTemplate.getForObject("/hello/sayHello", Map.class);
System.out.println(result.get("message"));
}
}
@Controller
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/hello/sayHello")
public @ResponseBody Object helloInfo() {
Map map = new HashMap<>();
map.put("message", helloService.sayHello());
return map;
}
}