Mockito - Mocking overloaded methods with Varargs parameters - java

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

Related

JUnit 5, how to get parameterized test parameter from BeforeEach callback?

I have the following set of methods in different classes:
#ParameterizedTest
#MethodSource("com.myapp.AppleProvider#getApplesDependingOnConditions")
public void testSomething(Apple apple) {
SomeContainer.getInstance().setApple(apple)
// ...
}
The problem is that I cannot avoid copy/pase of the following
name argument for each test call
The very first line of each test - SomeContainer.getInstance().setApple(apple)
I tried to use extension points - BeforeTestExecutionCallback and BeforeEachCallback, but they don't seem to have ability to get parameter with which they are being called.
According to https://github.com/junit-team/junit5/issues/1139 and https://github.com/junit-team/junit5/issues/944 it's not possible to access argument passed to test from extension points yet and parameterized tests don't work for BeforeEach callbacks.
So I'm basically looking for any workaround so that my test could look like:
#MyAwesomeTest
public void testSomething() {
// ...
}
Where #MyAwesomeTest encapsulates two annotations above.
What I've already found:
In extension points the following data is available: displayname, method or tags. If I pass argument into the each test method (though it's very undesirable) looks like I can rely on displayname since it'll reflect argument passed to the method call for a particular parameter.
I'm trying to find out whether there're any other ways without need to add argument into each method.
I think you could cheat to get most of the way there:
public static Stream<String> apples() {
return com.myapp.AppleProvider
.getApplesDependingOnConditions()
.stream()
.peek(apple -> SomeContainer.getInstance().setApple(apple))
.map(apple -> { /* convert to name string */ })
}
#ParameterizedTest
#MethodSource("apples")
public void testSomething(String name) {
// ...
}

how to intercept parameter with mockito for a normal array of objects?

This might sound like a very easy question but I am really struggling to archive the solution.
Normally I mock and match quite easily my arguments.
Now I am matching a method that is like this:
getAppFacebookClient(page, V2_11).publish(destination, JsonObject.class, parameters.asArray());
this is for a facebook application and the parameters is a list of a custom Object. the asArray[] method was created in the class and basically does something like this:
public Parameter[] asArray() {
return parameters.toArray(new Parameter[parameters.size()]);
}
and the Parameter of this return is of the type com.restfb.Parameter
So, I am basically doing this
when(client.publish(anyString(), eq(JsonObject.class), any(com.restfb.Parameter[].class))).thenReturn(result);
but seems like it is never taken and of course I cannot manipulate result,
Any idea how could I mock this kind of objects in a proper way?
I also tried the other way
doReturn(result).when(client).publish(anyString(), eq(JsonObject.class), any(com.restfb.Parameter[].class));
Your code is correct ... unless the publish uses varargs!
In such a case you need to use any() / anyVararg() matcher.
Consider:
#Mock Thingy testee;
interface Thingy {
int f(String... arg);
}
#Test
public void test() {
// given
// works only if signature is `f(String[] arg)`
// when(this.testee.f(Mockito.any(String[].class))).thenReturn(42);
when(this.testee.f(Mockito.any())).thenReturn(42); // anyVararg() is deprecated
// when
final int result = this.testee.f(new String[] { "hello", "world" });
// then
assertThat(result, comparesEqualTo(42));
// works only if signature is `f(String[] arg)`
// verify(this.testee).f(Mockito.any(String[].class));
verify(this.testee).f(Mockito.any());
}

Modify input parameter of a void function and read it afterwards

I have a rather complex java function that I want to test using jUnit and I am using Mockito for this purpose. This function looks something like this:
public void myFunction (Object parameter){
...
doStuff();
...
convert(input,output);
...
parameter.setInformationFrom(output);
}
The function convert sets the attributes of output depending on input and it's a void type function, although the "output" parameter is what is being used as if it were returned by the function. This convert function is what I want to mock up as I don't need to depend on the input for the test, but I don't know how to do this, as I am not very familiar with Mockito.
I have seen basic cases as when(something).thenReturn(somethingElse)
or the doAnswer method which I understand is similar to the previous one but more logic can be added to it, but I don't think these cases are appropriate for my case, as my function does not have a return statement.
If you want the mocked method to call a method on (or otherwise alter) a parameter, you'll need to write an Answer as in this question ("How to mock a void return method affecting an object").
From Kevin Welker's answer there:
doAnswer(new Answer() {
Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
((MyClass)args[0]).myClassSetMyField(NEW_VALUE);
return null; // void method, so return null
}
}).when(mock).someMethod();
Note that newer best-practices would have a type parameter for Answer, as in Answer<Void>, and that Java 8's lambdas can compress the syntax further. For example:
doAnswer(invocation -> {
Object[] args = invocation.getArguments();
((MyClass)args[0]).myClassSetMyField(NEW_VALUE);
return null; // void method in a block-style lambda, so return null
}).when(mock).someMethod();

Mockito: InvalidUseOfMatchersException

I have a command line tool that performs a DNS check. If the DNS check succeeds, the command proceeds with further tasks. I am trying to write unit tests for this using Mockito. Here's my code:
public class Command() {
// ....
void runCommand() {
// ..
dnsCheck(hostname, new InetAddressFactory());
// ..
// do other stuff after dnsCheck
}
void dnsCheck(String hostname, InetAddressFactory factory) {
// calls to verify hostname
}
}
I am using InetAddressFactory to mock a static implementation of the InetAddress class. Here's the code for the factory:
public class InetAddressFactory {
public InetAddress getByName(String host) throws UnknownHostException {
return InetAddress.getByName(host);
}
}
Here's my unit test case:
#RunWith(MockitoJUnitRunner.class)
public class CmdTest {
// many functional tests for dnsCheck
// here's the piece of code that is failing
// in this test I want to test the rest of the code (i.e. after dnsCheck)
#Test
void testPostDnsCheck() {
final Cmd cmd = spy(new Cmd());
// this line does not work, and it throws the exception below:
// tried using (InetAddressFactory) anyObject()
doNothing().when(cmd).dnsCheck(HOST, any(InetAddressFactory.class));
cmd.runCommand();
}
}
Exception on running testPostDnsCheck() test:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded.
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
Any input on how to solve this?
The error message outlines the solution. The line
doNothing().when(cmd).dnsCheck(HOST, any(InetAddressFactory.class))
uses one raw value and one matcher, when it's required to use either all raw values or all matchers. A correct version might read
doNothing().when(cmd).dnsCheck(eq(HOST), any(InetAddressFactory.class))
I had the same problem for a long time now, I often needed to mix Matchers and values and I never managed to do that with Mockito.... until recently !
I put the solution here hoping it will help someone even if this post is quite old.
It is clearly not possible to use Matchers AND values together in Mockito, but what if there was a Matcher accepting to compare a variable ? That would solve the problem... and in fact there is : eq
when(recommendedAccessor.searchRecommendedHolidaysProduct(eq(metas), any(List.class), any(HotelsBoardBasisType.class), any(Config.class)))
.thenReturn(recommendedResults);
In this example 'metas' is an existing list of values
It might help some one in the future: Mockito doesn't support mocking of 'final' methods (right now). It gave me the same InvalidUseOfMatchersException.
The solution for me was to put the part of the method that didn't have to be 'final' in a separate, accessible and overridable method.
Review the Mockito API for your use case.
May be helpful for somebody. Mocked method must be of mocked class, created with mock(MyService.class)
For my case, the exception was raised because I tried to mock a package-access method. When I changed the method access level from package to protected the exception went away. E.g. inside below Java class,
public class Foo {
String getName(String id) {
return mMap.get(id);
}
}
the method String getName(String id) has to be AT LEAST protected level so that the mocking mechanism (sub-classing) can work.
Inspite of using all the matchers, I was getting the same issue:
"org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
1 matchers expected, 3 recorded:"
It took me little while to figure this out that the method I was trying to mock was a static method of a class(say Xyz.class) which contains only static method and I forgot to write following line:
PowerMockito.mockStatic(Xyz.class);
May be it will help others as it may also be the cause of the issue.
Another option is to use a captor: https://www.baeldung.com/mockito-argumentcaptor
// assume deliver takes two values
#Captor
ArgumentCaptor<String> address; // declare before function call.
Mockito.verify(platform).deliver(address.capture(), any());
String value = address.getValue();
assertEquals(address == "some#thing.com");
Captors are especially useful if say one member of the object you want to capture could be a random ID and another is something you can validate against.
Do not use Mockito.anyXXXX(). Directly pass the value to the method parameter of same type.
Example:
A expected = new A(10);
String firstId = "10w";
String secondId = "20s";
String product = "Test";
String type = "type2";
Mockito.when(service.getTestData(firstId, secondId, product,type)).thenReturn(expected);
public class A{
int a ;
public A(int a) {
this.a = a;
}
}

Can Mockito capture arguments of a method called multiple times?

I have a method that gets called twice, and I want to capture the argument of the second method call.
Here's what I've tried:
ArgumentCaptor<Foo> firstFooCaptor = ArgumentCaptor.forClass(Foo.class);
ArgumentCaptor<Foo> secondFooCaptor = ArgumentCaptor.forClass(Foo.class);
verify(mockBar).doSomething(firstFooCaptor.capture());
verify(mockBar).doSomething(secondFooCaptor.capture());
// then do some assertions on secondFooCaptor.getValue()
But I get a TooManyActualInvocations Exception, as Mockito thinks that doSomething should only be called once.
How can I verify the argument of the second call of doSomething?
I think it should be
verify(mockBar, times(2)).doSomething(...)
Sample from mockito javadoc:
ArgumentCaptor<Person> peopleCaptor = ArgumentCaptor.forClass(Person.class);
verify(mock, times(2)).doSomething(peopleCaptor.capture());
List<Person> capturedPeople = peopleCaptor.getAllValues();
assertEquals("John", capturedPeople.get(0).getName());
assertEquals("Jane", capturedPeople.get(1).getName());
Since Mockito 2.0 there's also possibility to use static method Matchers.argThat(ArgumentMatcher). With the help of Java 8 it is now much cleaner and more readable to write:
verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("OneSurname")));
verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("AnotherSurname")));
If you're tied to lower Java version there's also not-that-bad:
verify(mockBar).doSth(argThat(new ArgumentMatcher<Employee>() {
#Override
public boolean matches(Object emp) {
return ((Employee) emp).getSurname().equals("SomeSurname");
}
}));
Of course none of those can verify order of calls - for which you should use InOrder :
InOrder inOrder = inOrder(mockBar);
inOrder.verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("FirstSurname")));
inOrder.verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("SecondSurname")));
Please take a look at mockito-java8 project which makes possible to make calls such as:
verify(mockBar).doSth(assertArg(arg -> assertThat(arg.getSurname()).isEqualTo("Surname")));
If you don't want to validate all the calls to doSomething(), only the last one, you can just use ArgumentCaptor.getValue(). According to the Mockito javadoc:
If the method was called multiple times then it returns the latest captured value
So this would work (assumes Foo has a method getName()):
ArgumentCaptor<Foo> fooCaptor = ArgumentCaptor.forClass(Foo.class);
verify(mockBar, times(2)).doSomething(fooCaptor.capture());
//getValue() contains value set in second call to doSomething()
assertEquals("2nd one", fooCaptor.getValue().getName());
You can also use #Captor annotated ArgumentCaptor. For example:
#Mock
List<String> mockedList;
#Captor
ArgumentCaptor<String> argCaptor;
#BeforeTest
public void init() {
//Initialize objects annotated with #Mock, #Captor and #Spy.
MockitoAnnotations.initMocks(this);
}
#Test
public void shouldCallAddMethodTwice() {
mockedList.add("one");
mockedList.add("two");
Mockito.verify(mockedList, times(2)).add(argCaptor.capture());
assertEquals("one", argCaptor.getAllValues().get(0));
assertEquals("two", argCaptor.getAllValues().get(1));
}
With Java 8's lambdas, a convenient way is to use
org.mockito.invocation.InvocationOnMock
when(client.deleteByQuery(anyString(), anyString())).then(invocationOnMock -> {
assertEquals("myCollection", invocationOnMock.getArgument(0));
assertThat(invocationOnMock.getArgument(1), Matchers.startsWith("id:"));
}
First of all: you should always import mockito static, this way the code will be much more readable (and intuitive) - the code samples below require it to work:
import static org.mockito.Mockito.*;
In the verify() method you can pass the ArgumentCaptor to assure execution in the test and the ArgumentCaptor to evaluate the arguments:
ArgumentCaptor<MyExampleClass> argument = ArgumentCaptor.forClass(MyExampleClass.class);
verify(yourmock, atleast(2)).myMethod(argument.capture());
List<MyExampleClass> passedArguments = argument.getAllValues();
for (MyExampleClass data : passedArguments){
//assertSometing ...
System.out.println(data.getFoo());
}
The list of all passed arguments during your test is accessible via the argument.getAllValues() method.
The single (last called) argument's value is accessible via the argument.getValue() for further manipulation / checking or whatever you wish to do.

Categories

Resources