今天分享一个在微服务中restful接口的单元测试例子,主要涉及三方面:内部调用的mock、接口DTO的序列化和反序列化、用MockMvc加速单元测试用例执行。

  1. 单元测试要求不依赖外部服务,这样才能够方便的支持在各种环境下执行,特别是CI环境。但是在微服务开发中最常见的依赖就是服务间的调用。好在有Mockito,我们可以方便的实现各种stub来mock掉feign client的调用。

  2. 为了尽可能的测试到微服务的调用过程,我们还需要模拟DTO的序列号和反序列化过程。在一个restful接口的单元测试中序列化过程如下,保证请求和响应结构都会经历序列化和反序列化过程:
    请求数据序列化-->请求数据发序列化
    响应数据序列化-->响应数据反序列化

  3. 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。


 

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐