mock feign Client in microservice for restful unit test

Posted by laflair13 on Fri, 31 Dec 2021 18:22:58 +0100

Today, I share a unit test example of restful interface in microservice, which mainly involves three aspects: mock of internal call, serialization and deserialization of interface DTO, and accelerating the execution of unit test cases with MockMvc.

  1. Unit testing requires that it does not rely on external services, so that it can be easily executed in various environments, especially CI environment. However, the most common dependency in microservice development is the invocation between services. Fortunately, with Mockito, we can easily implement various stub s to mock the call of feign client.

  2. In order to test the calling process of microservices as much as possible, we also need to simulate the sequence number and deserialization process of DTO. In the unit test of a restful interface, the serialization process is as follows to ensure that the request and response structures will undergo serialization and deserialization processes:
    Request data serialization -- > request data serialization
    Response data serialization -- > response data deserialization

  3. MockMvc can complete the service processing request simulating rest without starting the http service. Therefore, MockMvc is generally used to simulate restful services to speed up the execution of unit tests.

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);
}

The above is controller, service and feign client, and the following is test class. Automatically configure MockMvc through AutoConfigureMockMvc annotation, and all controllers of the current package will be loaded into MockMvc. By injecting WebApplicationContext and then using it to obtain the bean s in the container, it is convenient to replace the feignClient field with a mock object with reflection before the unit test starts. Then use mockito in the unit test when(userApiClient.profile(Mockito.any())). thenReturn(new ProfileResp("0000","success")); Insert the stub to make the replaced userapiclient return the results we need when calling the profile interface again.
objectMapper is used to simulate jackson serialization and deserialization of spring web.
The complete code is as follows:
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());
    }
}

Tip: in order to facilitate reading, this article omits the code such as import. This example uses junit5.


 

Topics: Java REST Spring Testing Microservices