「別試著理解它,去感受它。」 -《TENET天能》
經過上篇測試Service層後,今天要來測試Controller層,由於Controller是透過用戶端使用HTTP呼叫的,所以我們要模擬網路的形式,使用MockMvc 來實作Controller層的測試。
透過輸入URL對Controller進行測試,如果需要啟動伺服器,建立http client進行測試,這樣會使得測試變得很麻煩,比如,啟動速度慢,測試驗證不方便,依賴網路環境等。
MockMvc這個spring framework 實現了對Http請求的模擬端點測試,能夠直接使用網路的形式,轉換到Controller的呼叫,不依賴網路環境,提供了一套驗證的工具,這樣可以使得請求的驗證統一且方便。
pom.xml
Spring Boot 預設建立專案時就會引入spring-boot-starter-test
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
首先要建立一個測試檔案
Spring Boot專案撰寫單元測試要先在類別前加入@SpringBootTest
測試標註。
並用@AutoConfigureMockMvc
啟動時自動注入MockMvc
用@MockBean
創造一個假的Service對象。
@SpringBootTest
@AutoConfigureMockMvc
public class TestTodoController {
@Autowired
private MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@MockBean
TodoService todoService;
}
介紹一下MockMvc的屬性
mockMvc.perform
執行一個請求,並對應到controller。mockMvc.andExpect
期待並驗證回應是否正確。mockMvc.andReturn
最後回應的值(body),可以再利用這個值,做其他Assert驗證[GET] /api/todos 端點
@RestController
@RequestMapping("/api")
public class TodoController {
@Autowired
TodoService todoService;
@GetMapping("/todos")
public ResponseEntity getTodos() {
Iterable<Todo> todoList = todoService.getTodos();
return ResponseEntity.status(HttpStatus.OK).body(todoList);
}
}
測試[GET] /api/todos
@Test
public void testGetTodos() throws Exception {
// 設定資料
List<Todo> expectedList = new ArrayList();
Todo todo = new Todo();
todo.setTask("洗衣服");
todo.setId(1);
expectedList.add(todo);
// 模擬todoService.getTodos() 回傳 expectedList
Mockito.when(todoService.getTodos()).thenReturn(expectedList);
// 模擬呼叫[GET] /api/todos
String returnString = mockMvc.perform(MockMvcRequestBuilders.get("/api/todos")
.accept(MediaType.APPLICATION_JSON ))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
Iterable<Todo> actualList = objectMapper.readValue(returnString, new TypeReference<Iterable<Todo>>() {
});
// 判定回傳的body是否跟預期的一樣
assertEquals(expectedList, actualList);
}
[POST] /api/todos 端點
@RestController
@RequestMapping("/api")
public class TodoController {
@Autowired
TodoService todoService;
@PostMapping("/todos")
public ResponseEntity createTodo(@RequestBody Todo todo) {
Integer rlt = todoService.createTodo(todo);
return ResponseEntity.status(HttpStatus.CREATED).body(rlt);
}
}
測試[POST] /api/todos
@Test
public void testrCreateTodo() throws Exception {
// 設定資料
Todo mockTodo = new Todo();
mockTodo.setId(1);
mockTodo.setTask("洗衣服");
mockTodo.setStatus(1);
JSONObject todoObject = new JSONObject();
todoObject.put("id", 1);
todoObject.put("task", "洗衣服");
// 模擬todoService.createTodo(todo) 回傳 id 1
Mockito.when(todoService.createTodo(mockTodo)).thenReturn(1);
// 模擬呼叫[POST] /api/todos
String actual = mockMvc.perform(MockMvcRequestBuilders.post("/api/todos")
.accept(MediaType.APPLICATION_JSON) //response 設定型別
.contentType(MediaType.APPLICATION_JSON) // request 設定型別
.content(String.valueOf(todoObject))) // body 內容
.andExpect(status().isCreated()) // 預期回應的status code 為 201(Created)
.andReturn().getResponse().getContentAsString();
}
原來的[DELTE] /api/todos/{id}
@RestController
@RequestMapping("/api")
public class TodoController {
@Autowired
TodoService todoService;
@DeleteMapping("/todos/{id}")
public ResponseEntity deleteTodo(@PathVariable Integer id) {
Boolean rlt = todoService.deleteTodo(id);
if (!rlt) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Id 不存在");
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).body("");
}
}
測試[DELETE] /api/todos
@Test
public void testDeleteTodoSuccess() throws Exception {
// 模擬todoService.deleteTodo(1) 成功回傳true
Mockito.when(todoService.deleteTodo(1)).thenReturn(true);
// 模擬呼叫[DELETE] /api/todos/{id}
mockMvc.perform(MockMvcRequestBuilders.delete("/api/todos/1")
.accept(MediaType.APPLICATION_JSON_UTF8 ) //response 設定型別
.contentType(MediaType.APPLICATION_JSON)) // request 設定型別
.andExpect(status().isNoContent()); // 預期回應的status code 應為 204(No Content)
}
@Test
public void testDeleteTodoIdNotExist() throws Exception {
//模擬delete id 不存在,所以todoService.deleteTodo(100)回傳false
Mockito.when(todoService.deleteTodo(100)).thenReturn(false);
// 模擬呼叫[DELETE] /api/todos/{id}
mockMvc.perform(MockMvcRequestBuilders.delete("/api/todos/100")
.accept(MediaType.APPLICATION_JSON_UTF8 ) //response 設定型別
.contentType(MediaType.APPLICATION_JSON)) // request 設定型別
.andExpect(status().isBadRequest()); // 預期回應的status code 為 400(Bad Request)
}
呼~總算寫完測試了,但[PUT]/api/todos/{id}出了點問題,我還在debug中,之後成功抓到蟲後,會再更新文章