Does matching with argThat only work for methods w

2019-07-16 07:26发布

问题:

I have the following method invocation in my class under test:

class ClassUnderTest {
   [...]
    restTemplate.getForEntity(url, MyResponseEntity.class, parameterMap);
   [...]
}

In the unit test of the class containing this line, I want to stub the getForEntity method such that it returns different responses for different entries in the parameterMap. Using BDDMockito, I'm trying to match that the desired parameter is part of the parameterMap by using argThat:

@InjectMocks
private ClassUnderTest classUnderTest;

@Mock
private RestTemplate restTemplate;

[...]

given(restTemplate.getForEntity(anyString(), any(Class.class), argThat(hasEntry("myKey", "myValue"))
   .willReturn(responseEntityA);

However, if I run the test such that the parameterMap contains (I verified this in the debugger) a key "myKey" and a value "myValue", I get a NullPointerException, because mockito does not seem to be able to match the method call with the stub I created.

On the other hand, using the following very general matching allows me run my tests without NPE, by providing a default response for all calls, however it does not allow me to define a custom response for a given parameter.

given(restTemplate.getForEntity(anyString(), any(Class.class), anyMap())
  .willReturn(defaultResponseEntity);

What is the reason for this? Does argThat only work for methods with a single parameter? Is there another way to match calls of the getEntity with a map that contains a certain (key, value) pair?

回答1:

argThat certainly works for more than one parameter, and there's nothing obviously wrong with what you're trying to do. I have a nagging hunch that it's selecting the wrong overload:

getForEntity(String url, Class<T> responseType, Map<String,?> urlVariables)
getForEntity(String url, Class<T> responseType, Object... urlVariables)

If the generics don't exactly match, Java may assume that your hasEntry best matches the Object... method where your anyMap matched the Map<String, ?>. In that case, you would be stubbing an overload you're not calling (Object...), and Mockito would revert to default return values for the one you're actually calling in your system under test (Map<String, ?>). Hovering over the method call in your IDE may shed light on which overload the compiler is matching.

To give Java a better hint, try a cast, which would look like this (warning, not tested or verified):

given(restTemplate.getForEntity(
         anyString(),
         any(Class.class),
         (Map<String, ?>) argThat(hasEntry("myKey", "myValue"))))
    .willReturn(responseEntityA);    


回答2:

I am not sure why the argThat is not working for you but you could try using thenAnswer and checking the arguments in your Answer.