MockMvc
用途: 模擬真實的API call
- @SpringBootTest、@AutoConfigureMockMvc
- 注入 MockMvc
package com.example.demo.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@AutoConfigureMockMvc
public class StudentControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void getById() throws Exception {
// RequestBuilder 設定要去發起的 http request 跟相關的參數
// MockMvcRequestBuilders.請求方式(url路徑)
RequestBuilder requestBuilder = MockMvcRequestBuilders
.get("/student/3");
// 要去執行這一個 http request
// mockMvc.perform() 等同於 Talend API Tester 中的 send 把資料送出 這個動作
// andExpect 驗證這次請求的返回結果
// MockMvcResultMatchers.status().is(200) 代表 http status code 是否為我們所預期的 200 ,是的話則單元測試成功
mockMvc.perform(requestBuilder)
.andExpect(MockMvcResultMatchers.status().is(200));
}
}
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/student/3"); 等同於 Talend API Tester 中的

mockMvc.perform 等同於 Talend API Tester 中的 send 把資料送出 這個動作

MockMvc 主體架構
- RequestBuilder 創建 http request , 設定 url 路徑 、請求參數、header
- mockMvc.perform() 執行 http request
| .andDo() | 輸出結果 |
| .andExpect() | 驗證結果 |
| .andReturn() | 取得結果 |
處理 http response
package com.example.demo.JPA;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
@SpringBootTest
@AutoConfigureMockMvc
public class StudentControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void getById2() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders
.get("/students/{sutdentId}",3);
mockMvc.perform(requestBuilder)
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.id",equalTo(3)))
.andExpect(jsonPath("$.name").value("Judy"));
}
}
Builder 模式

jsonPath
$ 代表 最外層的 json object . 代表 的 $.id 要去取得 json object 它裡面的 id 的值
線上練習 JSONPath: https://jsonpath.com/

MockMvc 常見寫法
.andDo(print()) 打印 API 的返回結果

.andReturn() 寫在最後面 ,可以取得完整的 API 執行結果出來
package com.example.demo.JPA;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@SpringBootTest
@AutoConfigureMockMvc
public class StudentControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void getById3() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders
.get("/students/{sutdentId}",3)
// 添加自定義的 request header
.header("headerName" ,"headerValue")
// 如果想要在 GET 請求中帶上一些參數 可以使用 queryParam 或 param
.queryParam("graduate" ,"true");
MvcResult mvcResult = mockMvc.perform(requestBuilder)
// .andReturn() 寫在最後面 ,可以取得完整的 API 執行結果出來
.andReturn();
// 可以取得這個 API 返回的 response body
String body = mvcResult.getResponse().getContentAsString();
}
}
MockMvc 常見寫法 2
Controller
package com.example.demo.controller;
import com.example.demo.JPA.Student;
import com.example.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@PostMapping("/students")
public ResponseEntity<Student> create(@RequestBody Student student){
Integer studentId = studentService.insert(student);
Student newStudent = studentService.getById(studentId);
return ResponseEntity.status(HttpStatus.CREATED).body(newStudent);
// HttpStatus.CREATED 201
}
}
POST contentType: application/json
@SpringBootTest
@AutoConfigureMockMvc
public class StudentControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void create(){
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("students")
// http request 上面加上一個 contentType 的 header
// 使用POST方法時 務必要加上 contentType 這行
// 這樣才可以在 request body 用json格式傳遞參數
.contentType(MediaType.APPLICATION_JSON)
.content("{\n" +
" \"name\": \"Han\",\n" +
" \"score\": 14.6,\n" +
" \"graduate\": false\n" +
"}\n" +
" ");
mockMvc.perform(requestBuilder)
.andExpect(status().is(201)); // controller 那邊是http status code: HttpStatus.CREATED
}
}

Mock測試 ,Mockito

假設今天想要測試 StudentService 這個 bean
但是如果 StudentDao 這個 bean 被別人改壞了
或是資料庫今天故障了
這樣在做 StudentService 的單元測試時就會測試失敗
因為 StudentService 會去依賴 StudentDao 還有資料庫的穩定性
但是 StudentService 實作的邏輯都是正常的
他只是受別人的牽連所以才導致他單元測試失敗
這時候為了提升 StudentService 單元測試的穩定性
這時候就可以將 StudentDao 這個 Bean 換成是一個假的 Bean

甚麼是Mock測試?
目的: 避免為了測試某一個單元測試 ,而去建構了整個 bean 的 dependency
作法: 創造一個假的 bean 去替換掉 Spring 容器中原有的 bean
使用一個假的 bean 去替換掉正常的 bean
讓這個假的 bean 固定返回我們指定的結果
想要測試Bean A 的實作是否正確 ,並且讓測試的變因少一點 ,
這時候與 Bean A 相關聯的 Bean B 就可以使用 Mock Bean B
讓 Bean B 固定返回指定的結果 ,這樣就能專注在測試 Bean A 的邏輯
不用擔心會被外部服務影響
Mock測試的目的
- 隔絕外部的服務
- 提升單元測試的穩定性
Mockito
Mockito 是 Spring Boot 中進行 Mock 測試的工具
功能:
- 模擬方法的返回值
- 模擬拋出 Exception
- 紀錄方法的使用次數、順序
Mockito 的用法
@MockBean: 產生一個假的bean ,替換掉Spring 容器中的bean
- 沒有定義的方法 ,預設返回 null
-
模擬方法的返回值
- when…thenReturn
- doReturn…when…
-
模擬噴出Exception
- when…thenThrow
// 方法不返回 void 的寫法 Mockito.when(studentDao.getById(Mockito.any())).thenThrow(new RuntimeException());- doThrow…when…
紀錄方法的使用次數、順序
限制:
- 不能 mock static 方法
- 不能 mock private 方法
- 不能 mock final class
@MockBean vs @SpyBean
@MockBean
產生一個假的bean ,替換掉Spring 容器中的bean
- 沒有定義的方法 ,預設返回null
@SpyBean
Spring 容器中的bean 仍舊是正常的bean ,只替換其中幾個方法
- 沒有定義的方法 ,預設使用真實的方法
- 如果只需要去mock特定方法時 ,就可以使用 @SpyBean 去達到這個目的
差異在於 @MockBean 整個bean 都假的 ,@SpyBean半真半假