今天分享一个在微服务中restful接口的单元测试例子,主要涉及三方面:内部调用的mock、接口DTO的序列化和反序列化、用MockMvc加速单元测试用例执行。
单元测试要求不依赖外部服务,这样才能够方便的支持在各种环境下执行,特别是CI环境。但是在微服务开发中最常见的依赖就是服务间的调用。好在有Mockito,我们可以方便的实现各种stub来mock掉feign client的调用。
为了尽可能的测试到微服务的调用过程,我们还需要模拟DTO的序列号和反序列化过程。在一个restful接口的单元测试中序列化过程如下,保证请求和响应结构都会经历序列化和反序列化过程:
请求数据序列化-->请求数据发序列化
响应数据序列化-->响应数据反序列化
MockMvc可以不启动http服务完成模拟rest的服务处理请求。因此一般使用MockMvc模拟restful服务来加速单元测试的执行。
HelloController.java
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@PostMapping("/user/profile")
public ProfileResp UserProfile(ProfileReq req) {
return helloService.profile(req);
}
}
HelloService.java
@Service
public class HelloService {
@Autowired
private UserApiClient userApiClient;
public ProfileResp profile(ProfileReq req) {
return userApiClient.profile(req);
}
}
UserApiClient.java
@FeignClient(name = "UserApiClient",
path = "/user")
public interface UserApiClient {
@PostMapping("/profile")
ProfileResp profile(ProfileReq req);
}
上面是controller、service和feign client,下面是测试类,通过AutoConfigureMockMvc注解自动配置MockMvc,会加载当前包的所有controller到MockMvc内。通过注入WebApplicationContext然后用其来获取容器中的bean,方便再单元测试开始前用反射将feignClient的字段替换成mock对象。再单元测试内使用Mockito.when(userApiClient.profile(Mockito.any())).thenReturn(new ProfileResp("0000","success"));
插入stub使替换掉的userApiClient再调用profile接口时返回我们需要的结果。
objectMapper则是用来模拟spring web的jackson序列化和反序列化操作。
完整代码如下:
DemoApplicationTests.java
@SpringBootTest
@AutoConfigureMockMvc
class DemoApplicationTests {
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private MockMvc mockMvc;
@Mock
private UserApiClient userApiClient;
@BeforeEach
void mockFeignClient() throws NoSuchFieldException, IllegalAccessException {
HelloService helloService = webApplicationContext.getBean(HelloService.class);
Field fieldUserApiClient = HelloService.class.getDeclaredField("userApiClient");
fieldUserApiClient.setAccessible(true);
fieldUserApiClient.set(helloService, this.userApiClient);
}
@Test
void userProfile() throws Exception {
Mockito.when(userApiClient.profile(Mockito.any()))
.thenReturn(new ProfileResp("0000","success"));
ProfileReq req = ProfileReq.builder().uid("1111").build();
byte[] data = mockMvc.perform(MockMvcRequestBuilders.post("/user/profile")
.content(objectMapper.writeValueAsBytes(req)))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn().getResponse().getContentAsByteArray();
ProfileResp resp = objectMapper.readValue(data, ProfileResp.class);
Assertions.assertNotNull(resp);
Assertions.assertEquals("0000", resp.getCode());
}
}
提示:本文为了方便阅读省略了import等代码。本例子使用的是junit5。