2. 使用Mockito

我们可以使用Mockito完全模拟RestTemplate的行为。在这种方法下,测试我们的服务就像进行其他涉及模拟的测试一样简单。

假设我们有一个简单的EmployeeService类,它通过HTTP获取员工详细信息:

@Service
public class EmployeeService {
    
    @Autowired
    private RestTemplate restTemplate;

    public Employee getEmployee(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_whenGetIsCalled_shouldReturnMockedObject() {
        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.getEmployee(id);
        Assertions.assertEquals(emp, employee);
    }
}

在上述JUnit测试类中,我们首先使用@Mock注解让Mockito创建一个空的RestTemplate实例。

然后,我们使用@InjectMocks注解将这个空实例注入到EmployeeService实例中。

最后,在测试方法中,我们使用Mockito的when/then支持定义了模拟对象的行为。

3. 使用Spring Test

Spring Test模块包含一个名为MockRestServiceServer的模拟服务器。通过这种方法,我们可以配置服务器,使其在我们的RestTemplate实例发送特定请求时返回特定的对象。此外,我们还可以在该服务器实例上进行verify()操作,检查所有期望是否已满足。

MockRestServiceServer实际上是通过使用MockClientHttpRequestFactory拦截HTTP API调用来工作的。根据我们的配置,它会创建预期请求和响应的列表。当RestTemplate实例调用API时,它会在自己的期望列表中查找请求,并返回相应的响应。

因此,它消除了在其他端口运行HTTP服务器以发送模拟响应的需要。

让我们使用MockRestServiceServer为相同的getEmployee()示例编写一个简单的测试:

@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.getEmployee(id);
        mockServer.verify();
        Assertions.assertEquals(emp, employee);                                                        
    }
}

在前一段代码中,我们使用了MockRestRequestMatchersMockRestResponseCreators的静态方法,以清晰、可读的方式定义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();
}

当我们在编写集成测试并仅需要模拟外部HTTP调用时,使用MockRestServiceServer非常有用。

4. 总结

在这篇简短的文章中,我们讨论了在编写单元测试时,通过HTTP模拟外部REST API调用的几种有效选项。

本文的源代码可以在GitHub上找到。