1. 簡介
我們經常會遇到需要執行某種 Web 請求的應用。在測試這種行為時,我們有一些 選項,適用於 Spring 應用。
In this quick tutorial, we’ll look at just a couple of ways of mocking such calls performed only through a RestTemplate.
我們將首先使用 Mockito,這是一種流行的 Mocking 庫進行測試。然後,我們將使用 Spring Test,它提供了一種創建 Mock 服務器的機制,用於定義服務器交互。
2. 使用 Mockito
我們可以使用 Mockito 完整地模擬 RestTemplate。 採用這種方法,測試我們的服務將像任何其他涉及模擬的測試一樣簡單。
2.1. 模擬 getForEntity()
假設我們有一個簡單的 EmployeeService 類,該類使用 getForEntity() 方法通過 HTTP 檢索員工信息:
@Service
public class EmployeeService {
@Autowired
private RestTemplate restTemplate;
public Employee getEmployeeWithGetForEntity(String id) {
ResponseEntity resp =
restTemplate.getForEntity("http://localhost:8080/employee/" + id, Employee.class);
return resp.getStatusCode() == HttpStatus.OK ? resp.getBody() : null;
}
}
現在我們來實施之前代碼的測試:
@ExtendWith(MockitoExtension.class)
public class EmployeeServiceTest {
@Mock
private RestTemplate restTemplate;
@InjectMocks
private EmployeeService empService = new EmployeeService();
@Test
public void givenMockingIsDoneByMockito_whenGetForEntityIsCalled_thenReturnMockedObject() {
Employee emp = new Employee(“E001”, "Eric Simmons");
Mockito
.when(restTemplate.getForEntity(
"http://localhost:8080/employee/E001", Employee.class))
.thenReturn(new ResponseEntity(emp, HttpStatus.OK));
Employee employee = empService.getEmployeeWithGetForEntity(id);
Assertions.assertEquals(emp, employee);
}
}在上述 JUnit 測試類中,我們首先要求 Mockito 使用 @Mock 註解創建一個佔位符 RestTemplate 實例。
然後,我們使用 @InjectMocks 註解對 EmployeeService 實例進行標註,將佔位符實例注入其中。
最後,在測試方法中,我們使用 Mockito 的 when/then 支持來定義 mock 的行為。
2.2. 模擬請求 exchange()</h3
exchange() 方法在 Spring RestTemplate 中是一種靈活的方式來執行 HTTP 請求。我們可以使用 GET、POST、PUT 和 DELETE 方法與 exchange() 方法一起使用。
假設我們有一個簡單的 EmployeeService 類,該類通過使用 exchange() 方法執行 HTTP 請求來獲取員工信息:
public Employee getEmployeeWithRestTemplateExchange(String id) {
ResponseEntity<Employee> resp = restTemplate.exchange("http://localhost:8080/employee/" + id,
HttpMethod.GET,
null,
Employee.class
);
return resp.getStatusCode() == HttpStatus.OK ? resp.getBody() : null;
}現在我們來為上述代碼編寫一個測試:
@ExtendWith(MockitoExtension.class)
public class EmployeeServiceUnitTest {
@Mock
private RestTemplate restTemplate;
@InjectMocks
private EmployeeService empService = new EmployeeService();
@Test
public void givenMockingIsDoneByMockito_whenExchangeIsCalled_thenReturnMockedObject(){
Employee emp = new Employee("E001", "Eric Simmons");
Mockito.when(restTemplate.exchange("http://localhost:8080/employee/E001",
HttpMethod.GET,
null,
Employee.class)
).thenReturn(new ResponseEntity(emp, HttpStatus.OK));
Employee employee = empService.getEmployeeWithRestTemplateExchange("E001");
assertEquals(emp, employee);
}
}在上述測試中,我們首先使用 @Mock 註解創建了一個模擬的 RestTemplate 實例。 隨後,我們注入了 EmployeeService 實例,並使用它來調用我們的邏輯。 最後,我們使用必要的參數模擬了 exchange() 方法並返回了員工實例。
3. 使用 Spring Test
Spring Test 模塊包含一個名為 MockRestServiceServer 的模擬服務器。 通過這種方法,我們配置服務器在通過我們的 RestTemplate 實例發送特定請求時返回一個特定的對象。 此外,我們還可以 verify() 該服務器實例,以檢查所有預期是否已滿足。
MockRestServiceServer 實際上通過使用 MockClientHttpRequestFactory 攔截 HTTP API 調用。 根據我們的配置,它會創建一個預期請求和相應的響應列表。 當 RestTemplate 實例調用 API 時,它會從其預期請求列表中查找請求,並返回相應的響應。
因此,它消除了在其他端口發送模擬響應的需要。
3.1. 模擬 getForEntity()</h3
讓我們使用 MockRestServiceServer 創建一個簡單的測試,用於相同的 getEmployeeWithGetForEntity() 示例:
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = SpringTestConfig.class)
public class EmployeeServiceMockRestServiceServerUnitTest {
@Autowired
private EmployeeService empService;
@Autowired
private RestTemplate restTemplate;
private MockRestServiceServer mockServer;
private ObjectMapper mapper = new ObjectMapper();
@BeforeEach
public void init() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
@Test
public void givenMockingIsDoneByMockRestServiceServer_whenGetIsCalled_thenReturnsMockedObject()() {
Employee emp = new Employee("E001", "Eric Simmons");
mockServer.expect(ExpectedCount.once(),
requestTo(new URI("http://localhost:8080/employee/E001")))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(mapper.writeValueAsString(emp))
);
Employee employee = empService.getEmployeeWithGetForEntity(id);
mockServer.verify();
Assertions.assertEquals(emp, employee);
}
}
在之前的片段中,我們使用了來自 <em >MockRestRequestMatchers</em > 和 <em >MockRestResponseCreators</em >> 的靜態方法,以清晰易讀的方式定義了 REST 調用中的期望和響應:
import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
import static org.springframework.test.web.client.response.MockRestResponseCreators.*;我們應該牢記,測試類中使用的 RestTemplate 實例應與 EmployeeService 類中使用的實例相同。為了確保這一點,我們在 Spring 配置中定義了一個 RestTemplate Bean,並在測試類和實現類中自動注入了該實例:
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}3.2. 模擬 exchange()
我們可以使用之前提到的類似方法,通過使用 MockRestServiceServer 來測試 getEmployeeWithRestTemplateExchange() 示例。
@Test
public void givenMockingIsDoneByMockRestServiceServer_whenExchangeIsCalled_thenReturnMockedObject() throws Exception {
Employee emp = new Employee("E001", "Eric Simmons");
mockServer.expect(ExpectedCount.once(), requestTo(new URI("http://localhost:8080/employee/E001")))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(mapper.writeValueAsString(emp)));
Employee employee = empService.getEmployeeWithRestTemplateExchange("E001");
mockServer.verify();
Assertions.assertEquals(emp, employee);
}如上所示測試表明,我們使用來自 MockRestRequestMatchers 和 MockRestResponseCreators 來定義 REST 調用中的期望和響應,使用 exchange() 方法。
使用 MockRestServiceServer 在編寫集成測試時非常有用,只需要模擬外部 HTTP 調用。
4. 結論
在本文中,我們探討了幾種有效的方法,用於在編寫單元測試時模擬外部 REST API 調用,這些調用通過 HTTP 進行。