1. 简介

在日常开发中,我们经常需要编写执行Web请求的应用程序。当我们对这类行为进行测试时,在Spring应用中有多种选择

本文将介绍几种只通过 RestTemplate 执行调用的模拟方法

我们将从使用 Mockito 这一流行的模拟库开始;接着使用 Spring Test 提供的机制,创建一个模拟服务器来定义服务交互行为。

2. 使用 Mockito 模拟 RestTemplate

Mockito 可以直接模拟整个 RestTemplate 实例。这种方式下,测试我们的服务就像测试任何其他涉及模拟对象的方法一样简单。

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

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

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

在这个 JUnit 测试类中:

  • 我们使用 @Mock 注解创建了一个虚拟的 RestTemplate 实例;
  • 使用 @InjectMocks 将该实例注入到 EmployeeService 中;
  • 在测试方法中通过 Mockito 的 when/then 语法 定义了模拟行为。

⚠️ 注意:这种方式适用于纯粹的单元测试场景,不需要启动任何实际的HTTP服务。

3. 使用 Spring Test 模块中的 MockRestServiceServer

Spring Test 模块提供了一个名为 MockRestServiceServer 的模拟服务器。通过这个工具,我们可以配置当某个特定请求通过 RestTemplate 发出时,返回指定的响应。此外,还可以通过 verify() 方法验证所有预期是否被满足。

MockRestServiceServer 的工作原理是通过拦截 HTTP API 调用(基于 MockClientHttpRequestFactory),根据我们配置的规则匹配请求并返回预设的响应。

这种做法的好处在于无需启动真实的 HTTP 服务就能完成对外部接口的模拟测试

下面是使用 MockRestServiceServer 对前面示例进行测试的代码:

@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() 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.getEmployee("E001");
        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.*;

⚠️ 重要提醒:为了确保 MockRestServiceServer 能正确拦截请求,测试类中使用的 RestTemplate 必须和 EmployeeService 中的是同一个实例。

为此,我们在 Spring 配置类中定义了一个 RestTemplate Bean,并在测试和实现类中都通过自动装配获取:

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

总结一句:当你写集成测试但只想模拟外部 HTTP 接口时,使用 MockRestServiceServer 是非常合适的方案

4. 结论

本文简要介绍了在编写单元测试时模拟外部 REST API 调用的有效方式。

你可以根据测试目标选择合适的策略:

  • 如果是纯单元测试,推荐使用 Mockito;
  • 如果需要模拟真实网络调用、但仍不想启动外部服务,推荐使用 Spring 的 MockRestServiceServer

源码可在 GitHub 查看:https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-resttemplate


原始标题:Mocking a RestTemplate in Spring