How Mockito handles overlapping matches with multiple arguments in thenReturn block

I have a block of test code that tries, in the general case, return two values ​​on subsequent calls, but in specific cases only the value associated with this case is returned. The code looks something like this:

when(mockObject.method(anyString())).thenReturn(string1, string2); when(mockObject.method(eq("expectedInput1"))).thenReturn(string1); when(mockObject.method(eq("expectedInput2"))).thenReturn(string2); 

The expected behavior is that when calling mockObject.method("foo") and mockObject.method("bar") , string1 and string2 should be returned respectively, but in fact there are two answers from string2 . Is this a bug in Mockito ? Or I misunderstand the correspondence of the Mockito template.

My assumption was that the last template matches what is returned, but when passing through this process, Mockito process each argument in the first thenReturn block separately? Is there any way around this behavior?

When I comment on the second second when making calls, the layouts behave as expected, so I assume that there is something specific about the matching behavior of the pairing.

Edit: this is in Mockito version 1.9.5

+6
source share
3 answers

I had this problem today. This is caused by layout calls to set up an interrupt that actually absorbs the hub already in place.

In this example, change the first line to

 when(mock.call(anyString())).thenReturn("","",string1,string2) 

This will give you two empty answers when you customize your other layout by returning string1 as the first useful return value.

Try also the doReturn option, which I think may not have these problems:

 doReturn(string1,string2).when(mock).call(anyString()); 

During installation, the plug is used in different ways.

So, I did some more research. Here's the function I played with based on the OP question:

  Function<String, String> function = mock(Function.class); when(function.apply(anyString())).thenReturn("A","B","C"); when(function.apply("Jim")).thenReturn("Jim"); when(function.apply("Bob")).thenReturn("Bob"); assertThat(function.apply("Jim")).isEqualTo("Jim"); assertThat(function.apply("Bob")).isEqualTo("Bob"); assertThat(function.apply("")).isEqualTo("A"); assertThat(function.apply("")).isEqualTo("B"); assertThat(function.apply("")).isEqualTo("C"); assertThat(function.apply("")).isEqualTo("C"); 

The above does not work in isEqualTo("A") because the two calls to configure mocks for Jim and Bob consume the return values ​​from the list provided in anyString() .

You may be tempted to change the order of the when clauses, but this fails because anyString() replaces special cases, so it also fails to execute.

The next version above works as expected:

  when(function.apply(anyString())).thenReturn("A","B","C"); doReturn("Jim") .when(function) .apply("Jim"); doReturn("Bob") .when(function) .apply("Bob"); assertThat(function.apply("Jim")).isEqualTo("Jim"); assertThat(function.apply("Bob")).isEqualTo("Bob"); assertThat(function.apply("")).isEqualTo("A"); assertThat(function.apply("")).isEqualTo("B"); assertThat(function.apply("")).isEqualTo("C"); assertThat(function.apply("")).isEqualTo("C"); 

This is due to the fact that the doReturn method, designed to modify pre-existing layouts in flight, does not actually include calling the method on the layout in order to set up a mockery.

You can use doReturn for all settings, and not for mixing between when ... thenReturn and doReturn .. when .. function() . As it happens, it's a little uglier:

  doReturn("A").doReturn("B").doReturn("C") .when(function) .apply(anyString()); 

There is no convenient varargs function that allows you to specify multiple returns in a sequence. The above has been verified and really works.

+8
source

One way to work around this problem is to use a regular expression to avoid overlapping the following:

 when(mockObject.method(eq("expectedInput1"))).thenReturn(string1); when(mockObject.method(eq("expectedInput2"))).thenReturn(string2); // Match with any input string that doesn't contain expectedInput1 neither expectedInput2 when(mockObject.method(matches("((?!expectedInput1|expectedInput2).)*"))) .thenReturn(string1, string2); 

Example:

 System.out.println("expectedInput1=" + mockObject.method("expectedInput1")); System.out.println("expectedInput2=" + mockObject.method("expectedInput2")); System.out.println("foo=" + mockObject.method("foo")); System.out.println("bar=" + mockObject.method("bar")); System.out.println("bar=" + mockObject.method("bar")); 

Output:

 expectedInput1=string1 expectedInput2=string2 foo=string1 bar=string2 bar=string2 

Another way could be to use ArgumentMatcher to prevent duplication:

 when(mockObject.method(eq("expectedInput1"))).thenReturn(string1); when(mockObject.method(eq("expectedInput2"))).thenReturn(string2); when( mockObject.method( argThat( new ArgumentMatcher<String>(){ @Override public boolean matches(final Object argument) { return !"expectedInput1".equals(argument) && !"expectedInput2".equals(argument); } } ) ) ).thenReturn(string1, string2); 

Another way could be to implement an Answer with something like this:

 when(mockObject.method(anyString())).thenAnswer( new Answer<String>() { Iterator<String> it = Arrays.asList(string1, string2).iterator(); String result; @Override public String answer(final InvocationOnMock invocation) throws Throwable { String argument = (String) invocation.getArguments()[0]; switch (argument) { case "expectedInput1" : return string1; case "expectedInput2" : return string2; default: if (it.hasNext()) { result = it.next(); } return result; } } } ); 
+2
source

It is difficult to say if there is an error or a function ... The fact is that when you call mockObject.method(eq("expectedInput1")) to complete the second stage, the first step is already in place. So this call returns string1 , which is then uselessly passed to when . Subsequent calls return string2 , and this includes a call to the last termination and subsequent calls during actual testing.

I can hardly see any elegant way around it unless I use a custom Answer like @Nicolas, although this seems like an overkill. Perhaps you can use custom matches instead of anyString() , which will essentially say "any string except the two." Thus, you will not have one matching the other.

R. S. Now that @Nicolas has edited his answer, this regex looks exactly the way I had in mind. Except that you don’t need to implement custom matches at all.

+2
source

Source: https://habr.com/ru/post/1011695/


All Articles