Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 935
Pavan Kumar Avatar asked Feb 25 '26 13:02

Pavan Kumar


1 Answers

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
like image 128
Pavan Kumar Avatar answered Feb 27 '26 04:02

Pavan Kumar



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!