springboot提供了 spirng-boot-starter-test
以供開發者使用單元測試,在引入 spring-boot-starter-test
依賴後:html
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
複製代碼
其中包含如下幾個庫:java
下面咱們將從Service層和Controller層的角度來簡單介紹下單元測試mysql
在SpringBoot 2.0中,建立一個Service的單元測試,代碼以下:web
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceImplTest {
@Autowired
private UserService userService;
@Test
public void insertUser() {
User user = new User();
user.setUsername("li ning");
user.setPassword("123456");
userService.insertUser(user);
}
}
複製代碼
上面的測試很是簡單,主要須要注意兩個註解: @RunWith
和@SpringBootTest
spring
SpringRunner
,它實際上繼承了 SpringJUnit4ClassRunner
類,而 SpringJUnit4ClassRunner
這個類是一個針對Junit 運行環境的自定義擴展,用來標準化在Springboot環境下Junit4.x的測試用例使用@SpringBootTest
的webEnvironment
屬性定義運行環境:sql
首先建立一個Controller,代碼以下:數據庫
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/user")
public String userMapping(@RequestBody User user){
userService.insertUser(user);
return "ok";
}
}
複製代碼
而後建立Controller的單元測試,通常有兩種建立方法。json
默認狀況下,@SpringBootTest 不會啓動服務器,若是需針對此模擬環境測試Web端點,能夠以下配置 MockMvc:安全
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void userMapping() throws Exception {
String content = "{\"username\":\"pj_mike\",\"password\":\"123456\"}";
mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.POST, "/user")
.contentType("application/json").content(content))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("ok"));
}
}
複製代碼
這裏有一個 @AutoConfigureMockMvc註解,該註解表示啓動測試的時候自動注入 MockMvc,而這個MockMvc有如下幾個基本的方法:springboot
perform
: 執行一個RequestBuilder請求,會自動執行SpringMVC的流程並映射到相應的控制器執行處理。andExpect
: 添加RequsetMatcher驗證規則,驗證控制器執行完成後結果是否正確andDo
: 添加ResultHandler結果處理器,好比調試時打印結果到控制檯andReturn
: 最後返回相應的MvcResult,而後進行自定義驗證/進行下一步的異步處理這裏有一個小技巧,通常來講對於一個controller中每每有不止一個Request請求須要測試,敲打MockMvcRequestBuilders與MockMvcResultMatchers會顯得比較繁瑣,有一個簡便的方法就是將這兩個類的方法使用
import static
靜態導入,而後就能夠直接使用兩個類的靜態方法了。而後代碼就變成以下所示:
...
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void userMapping() throws Exception {
String content = "{\"username\":\"pj_mike\",\"password\":\"123456\"}";
mockMvc.perform(request(HttpMethod.POST, "/user")
.contentType("application/json").content(content))
.andExpect(status().isOk())
.andExpect(content().string("ok"));
}
}
複製代碼
另外,若是是隻想關注Web層而不是啓動完整的ApplicationContext,能夠考慮使用 @WebMvcTest 註解,該註解不能與@SpringBootTest搭配使用,並且它只關注Web層面,至於涉及到數據層的時候,須要引入相關依賴,關於這個註解更多的介紹請參閱官方文檔: docs.spring.io/spring-boot…
除了上面用 @AutoConfigureMockMvc 註解直接自動注入 MockMvc的方式,咱們還能夠利用MockMvcBuilder來構建MockMvc對象,示例代碼以下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest4 {
@Autowired
private WebApplicationContext web;
private MockMvc mockMvc;
@Before
public void setupMockMvc() {
mockMvc = MockMvcBuilders.webAppContextSetup(web).build();
}
@Test
public void userMapping() throws Exception {
String content = "{\"username\":\"pj_m\",\"password\":\"123456\"}";
mockMvc.perform(request(HttpMethod.POST, "/user")
.contentType("application/json").content(content))
.andExpect(status().isOk())
.andExpect(content().string("ok"));
}
}
複製代碼
在@SpringBootTest註解中設置屬性 webEnvironment = WebEnvironment.RANDOM_PORT
,每次運行的時候會隨機選擇一個可用端口。咱們也能夠還使用 @LoalServerPort
註解用於本地端口號。下面是測試代碼:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerTest3 {
@Autowired
private TestRestTemplate testRestTemplate;
@Test
public void userMapping() throws Exception {
User user = new User();
user.setUsername("pj_pj");
user.setPassword("123456");
ResponseEntity<String> responseEntity = testRestTemplate.postForEntity("/user", user, String.class);
System.out.println("Result: "+responseEntity.getBody());
System.out.println("狀態碼: "+responseEntity.getStatusCodeValue());
}
}
複製代碼
上面的代碼中有一個關鍵的類——TestRestTemplate, TestRestTemplate是Spring的RestTemplate的一種替代品,可用於集成測試,更RestTemplate的使用功能方法相似,通常用於真實web環境測試中,關於該類更加詳細的用法參考官方文檔: docs.spring.io/spring-boot…
單元測試的時候,若是不想形成垃圾數據,能夠開啓事務功能,在方法或類頭部添加 @Transactional
註解便可,在官方文檔中對此也有說明:
If your test is @Transactional, it rolls back the transaction at the end of each test method by default. However, as using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case
解讀一下,在單元測試中使用 @Transactional
註解,默認狀況下在測試方法的末尾會回滾事務。然而有一些特殊狀況須要注意,當咱們使用 RANDOM_PORT
或DEFINED_PORT
這種安排隱式提供了一個真正的Servlet環境,因此HTTP客戶端和服務器將在不一樣的線程中運行,從而分離事務,這種狀況下,在服務器上啓動的任何事務都不會回滾。
固然若是你想關閉回滾,只要加上 @Rollback(false)
註解便可,@Rollback
表示事務執行完回滾,支持傳入一個value,默認true即回滾,false不回滾。
還有一種狀況須要注意,就是若是你使用的數據庫是MySQL,有時候會發現加了註解 @Transactionl
也不會回滾,那麼你就要查看一下你的默認引擎是否是InnoDB,若是不是就要改爲 InnoDB。
MyISAM 與 InnoDB是mysql目前比較經常使用的兩個數據庫引擎,MyISAM與InnoDB的主要的不一樣點在於性能和事務控制上,這裏簡單介紹下二者的區別與轉換方法:
若是你的數據表是MyISAM引擎,因爲它不支持事務,在單元測試中添加事務註解,測試方法也是不會回滾的。
修改默認引擎
mysql> show variables like '%storage_engine%';
複製代碼
mysql> show create table user;
複製代碼
mysql> ALTER TABLE user ENGINE=INNODB;
複製代碼
注意
這裏還有一點須要注意的地方,當咱們使用Spring Data JPA時,若是沒有指定MySQL建表時的存儲引擎,默認狀況下會使用MySQL的MyISAM,這也是一個坑點,這種狀況下,你在單元測試使用@Transactional
註解,回滾不會起做用。
解決方法是將 hibernate.dialect
屬性配置成hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
,指定MySQL建表的時候使用 InnoDB引擎,示例配置文件以下:
spring:
jpa:
# 數據庫類型
database: mysql
# 輸出日誌
show-sql: true
properties:
hibernate:
# JPA配置
hbm2ddl.auto: update
# mysql存儲類型配置
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
複製代碼
上面簡單總結了springboot下如何使用單元測試,關於單元測試更加詳細的介紹請參閱官方文檔:docs.spring.io/spring-boot…