0

I have a Spring Boot application which uses Feign Client to call a microservice to add users to the User table when a new department is created (new department will be inserted into the Department table). The request looks like:

Request:

 {
  "department": "math",
  "usernameList": ["aaa", "bbb", "ccc"]
 }

The User model:

 public class User {
    private String username;
 }

The Feign client:

 import org.springframework.cloud.openfeign.FeignClient;
 @FeignClient(name = "user-client", url = "/.../user", configuration = UserConfiguration.class)

 public interface UserClient {

     @RequestMapping(method = RequestMethod.POST, value = "users")
     User createUser(User user);
 }

UserService:

@Service
public class UserService {

private final UserClient userClient;       

public UserResponse createUser(@Valid Request request);

     List<User> userList = request.getUsernameList()
           .stream()
           .map(username -> userClient.createUser(mapToUser(username)) 
           .collect(Collectors.toList());
 ......
}

The above code worked and I was able to add 3 users into the database. The userList has 3 correct username. However, when I ran the junit test below, it seemed that only the last userResp ("ccc") was returned 3 times as mock response. When I ran the junit test in debug mode, I saw that each time the thenReturn(userResp) had the correct userResp, but in the UserService, the userList ended up containing 3 "ccc", rather than a list of "aaa, bbb, ccc". I tried using the FOR loop in the UserService rather than the stream, the result was the same, so it wasn't because of the stream. I also tried to remove the FOR loop in the Junit and just called the mock 3 times, same result. I am not sure if this has something to do with the Feign client mocking or if I did something wrong in my test case. Can someone please help?

My Junit:

 public class UserTest {

    @MockBean
    private UserClient userClient;  

    @Test
    public void testAddUser() throws Exception {

        for (int i=1; i<=3; i++) {

             User userResp = new User();

              if (i==1) {
             userResp.setUsername("aaa");
            // mock response
       Mockito.when(userClient.createUser(ArgumentMatchers.any(User.class)))
         .thenReturn(userResp);
           }
           if (i==2) {
             userResp.setUsername("bbb");
            // mock response
       Mockito.when(userClient.createUser(ArgumentMatchers.any(User.class)))
         .thenReturn(userResp);
           }
        if (i==3) {
             userResp.setUsername("ccc");
            // mock response
       Mockito.when(userClient.createUser(ArgumentMatchers.any(User.class)))
         .thenReturn(userResp);
           }
       }

       // invoke the real url
       MvcResult result = mockMvc.perform(post("/users")
            .content(TestUtils.toJson(userRequest, false))
            .contentType(contentType))
            .andDo(print())
            .andExpect(status().isCreated())
            .andReturn();  
    }
jlp
  • 1,656
  • 6
  • 33
  • 48
  • Its returning "ccc" in every case because you are mocking the same call every time Mockito.when(userClient.createUser(ArgumentMatchers.any(User.class))) .thenReturn(userResp); and since "ccc" is the last mock its returning "ccc" – yash sugandh Oct 19 '19 at 19:50
  • when i==1, the userResp has "aaa", shouldn't it return "aaa" the first time? Or Junit treated 3 calls as 1 call because it was the same mock call? – jlp Oct 19 '19 at 19:58
  • No, because when your actual service call is made the value of userResp is set to "ccc" – yash sugandh Oct 19 '19 at 20:00
  • I see. So it's not like when the service calls createUser each time, the mock gets called. All mocks were executed before the service call was invoked.... – jlp Oct 19 '19 at 20:03
  • exactly, I have suggested a way that can help you with your case – yash sugandh Oct 19 '19 at 20:06

1 Answers1

3

To make the method return different values for the subsequent call you can use

Mockito.when(userClient.createUser(ArgumentMatchers.any(User.class)))
            .thenReturn("aaa")
            .thenReturn("bbb")
            .thenReturn("ccc"); //any

// Or a bit shorter with varargs:

    Mockito.when(userClient.createUser(ArgumentMatchers.any(User.class)))
        .thenReturn("aaa", "bbb", "ccc"); //any
yash sugandh
  • 608
  • 4
  • 6
  • is this really possible to use? mockClient returns real object, compiler does not allow you to return a string instead, does it? When you use mock client, you are mocking the real response (string) and let the client to decode it to object.. But when you mock with `Mockito.when(..)` you return the clients return object, and bypass the decoding process. Is there a way to let mockClient return different results in subsequential call using `mockClient.ok(..)` method? – Zavael Feb 10 '21 at 19:57