简体   繁体   中英

Mockito - Mocking overloaded methods with Varargs parameters

Mockito is hard to use when we need to mock overloaded methods when one of them is using varargs. Consider the below methods from Spring's RestTemplate

void put(String url, Object request, Object... uriVariables) throws RestClientException;

void put(String url, Object request, Map<String, ?> uriVariables) throws RestClientException;

Mocking the second one is straight forward, but mocking the first one is not possible as using any() would result in an ambiguous method call matching both the methods and there is no alternative to match just Object...

Sharing the solution as Q & A that I arrived after putting some effort so as to help those in the same boat. All other alternatives welcome.

Solution to this can be attempted by making use of the feature to provide a defaultAnswer to the mock. The defaultAnswer will evaluate that the invocation is for the specific method and perform the needed action, and let the invocation follow the natural flow if the required method is not targeted.

This can be explained well with an example. Consider the two overloaded methods in the class below:

public class StringConcat {
    public String concatenate(int i, String... strings) {
        return i + Arrays.stream(strings).collect(Collectors.joining(","));
    }

    public String concatenate(int i, List<String> strings) {
        return i + strings.stream().collect(Collectors.joining(","));
    }
}

The second method can be mocked using Mockito like below:

StringConcat stringConcat = mock(StringConcat.class);
when(stringConcat.concatenate(anyInt(), anyList())).thenReturn("hardcoded value");

To represent varargs, we do not have anyVararg() method (deprecated and does not work, not sure if it worked in older versions). But the same can be handled by creating the mock with defaultAnswer like below:

@Test
void testWithDefaultAnswer(){
    // Creating mock object with default answer
    StringConcat stringConcat = mock(StringConcat.class, invocation -> {
        Method method = invocation.getMethod();
        if (method.getName().contains("concatenate") && 
               method.getParameters()[method.getParameters().length-1].isVarArgs()){
            if(invocation.getArguments().length>=method.getParameterCount()){
                List varArgParams = Arrays.stream(invocation.getArguments())
                          .skip(method.getParameterCount()-1)
                          .collect(Collectors.toList());
                return invocation.getArguments()[0]+":"
                      +varArgParams.toString(); // mocked result when varargs provided
            }
            return ""+invocation.getArguments()[0]; // mocked result when varargs not provided
        }
        return Answers.RETURNS_DEFAULTS.answer(invocation); // Ensures seamless mocking of any other methods
    });

    // Mock any non varargs methods as needed
    when(stringConcat.concatenate(anyInt(), anyList())).thenReturn("hardcoded"); // mocking as usual

    // Test the mocks
    System.out.println(stringConcat.concatenate(1, "a", "b")); // default answer based mock, with varargs provided
    System.out.println(stringConcat.concatenate(1)); // default answer based mock, without varargs provided
    System.out.println(stringConcat.concatenate(1, Lists.newArrayList("a", "b"))); // mocked non varargs method
}

Output:

1:[a, b]
1
hardcoded

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM