Hot questions for Using Mockito in variadic functions

Top 10 Java Open Source / Mockito / variadic functions

Question:

I am getting a "reference to make is ambiguous" compiler error that I don't understand.

I have these two methods

public static <T> T make(String name, Class<T> parentClass, 
                         boolean rethrowRuntimeExceptions, 
                         Object... params) throws DLException

 public static <T> T make(String name, Class<T> parentClass,
                          Object... params) throws DLException

This line of code is being marked as ambiguous

  String className = "clsNme";
  String one = "1";
  String two = "2";     
  SimpleFactory.make(className, Object.class, false, one, two);

Here is the error

both method <T#1>make(String,Class<T#1>,boolean,Object...) in SimpleFactory and method <T#2>make(String,Class<T#2>,Object...) in SimpleFactory match
    [javac]   where T#1,T#2 are type-variables:
    [javac]     T#1 extends Object declared in method <T#1>make(String,Class<T#1>,boolean,Object...)
    [javac]     T#2 extends Object declared in method <T#2>make(String,Class<T#2>,Object...)

Doesn't the presence of the boolean parameter make the first method a closer match than the second?

If it matters, this is part of a PowerMock test Here is the complete method

public void makeCallsMakeWithFalse() throws Throwable {
  Object expected = mock(Object.class);
  String className = "clsNme";

  String one = "1";
  String two = "2";

  spy(SimpleFactory.class);

  doReturn(expected).when(SimpleFactory.class);
  SimpleFactory.make(className, Object.class, false, one, two);  // causes error

  Object observed = SimpleFactory.make(className, Object.class, one, two); // doesn't cause error
  assertEquals(expected, observed);

  verifyStatic();
  SimpleFactory.make(className, Object.class, false, one, two);  // causes error

}

If it helps: I'm using javac 1.8.0_77, Mokito 1.10.19, and Powermock 1.6.3.


Answer:

The compiler first tries to find a matching signature thst does not involve autoboxing/unboxing or variable arity invocation. Variable arity invocation is when you invoke a varargs method by passing a parameter list as the last argument (as opposed to an array).

In your case, both involve variable arity invocation. When this happens, the most specific overload is chosen. For your situation, neither is considered more specific as defined in the JLS. This is essentially because neither of the types boolean and Object is a subtype of the other.

Simplifying your example a bit, the following does not compile.

static void foo(boolean b, Object... arr) {

}

static void foo(Object... arr) {

}

public static void main(String[] args) {
    foo(true);
}

The first version will not accept a single argument of type Object and the second will not accept a single argument of type boolean. Therefore neither is more specific. (Autoboxing only makes it look as if you can pass a boolean as an argument of type Object).

On the other hand, if you replace boolean by Boolean, it does compile because Boolean is a subtype of Object.

Question:

Given a class such as the "Target" class below:

class Target {

fun <R> target(vararg filter: String, mapper: (String) -> R): R {
    println(filter.contentDeepToString())
    return mapper("target")
}

}

And a test such as below:

@Test
fun test() {
    val mockTarget = mock(Target::class.java)
    Mockito.`when`(mockTarget.target<Int>("Hello World", any<(String) -> Int>()))
           .thenReturn(1)
}

I'm getting the Type mismatch: inferred type is ((String) -> Int)! but String was expected.

I know the vararg usually goes to the end of the function, in fact if you move the vararg to the end it compiles.

Once mockito looks to the bytecode, I'm wondering if it is possible to use mockito in such odd method.

Edit

The code runs as expected, the problem happens only when you try to mock.

fun main(args: Array<String>) {
    val result = Target().target("Hello World") {
        123
    }
    println("$result == 123? ${result == 123}")
}
// out:
// [Hello World]
// 123 == 123? true

Answer:

Use named argument. Add mapper = before your lambda, like this:

@Test
fun test() {
    val mockTarget = Mockito.mock(Target::class.java)
    Mockito.`when`(
        mockTarget.target<Int>(
            "Hello World",
            mapper = Mockito.any<(String) -> Int>()
        )
   ).thenReturn(1)
}

Question:

How to properly match varargs in Mockito answers how to match any varargs (including in Mockito 2) and how to match more precisely (e.g. using Hamcrest matchers, but in Mockito 1). I need the latter in Mockito 2. Is that possible?

In this test, the test using any passes, but the one with the ArgumentMatcher fails (using org.mockito:mockito-core:2.15.0):

package test.mockito;

import java.io.Serializable;
import java.util.Arrays;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import org.mockito.ArgumentMatcher;
import static org.mockito.Mockito.*;
import org.mockito.internal.matchers.VarargMatcher;

public class TestVarArgMatcher {
    interface Collaborator {
        int f(String... args);
    }

    @Test
    public void testAnyVarArg() {
        Collaborator c = mock(Collaborator.class);
        when(c.f(any())).thenReturn(6);
        assertEquals(6, c.f("a", "b", "c")); // passes
    }

    @Test
    public void testVarArg() {
        Collaborator c = mock(Collaborator.class);
        when(c.f(argThat(arrayContains("b")))).thenReturn(7);
        assertEquals(7, c.f("a", "b", "c")); // fails: expected:<7> but was:<0>
    }

    static <T extends Serializable> ArgumentMatcher<T[]> arrayContains(T element) {
        return new ArrayContainsMatcher<>(element);
    }

    private static class ArrayContainsMatcher<T> implements ArgumentMatcher<T[]>, VarargMatcher {
        private static final long serialVersionUID = 1L;
        private final T element;

        public ArrayContainsMatcher(T element) {
            this.element = element;
        }

        @Override
        public boolean matches(T[] array) {
            return Arrays.asList(array).contains(element);
        }
    }
}

BTW, class ArrayContainsMatcher is supposed to be inlined as anonymous class or lambda inside method arrayContains if implementing VarargMatcher is not necessary.


Answer:

When a method on a mock with vararg arguments is called, Mockito checks if the last matcher that was passed in to the when method is an ArgumentMatcher that implements the VarargMatcher interface. This is correct in your case.

Mockito then internally expands the list of matchers for the call by repeating this last matcher for every vararg argument so that in the end the internal list of arguments and the list of matchers have the same size. In your example this means that during the matching there are three arguments - "a", "b", "c" - and three matchers - three times the instance of the ArrayContainsMatcher.

Then Mockito tries to match each argument against the matcher. And here your code fails, because the argument is a String and the matcher needs a String[]. So the match fails and the mock returns the default value of 0.

So the important thing is that a VarargMatcher is not called with the array of vararg arguments, but repeatedly with every single argument.

To get the behaviour as you want it, you must implement a matcher that has internal state, and instead of using then to return a fixed value you need thenAnswer with code that evaluates the state.

import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.VarargMatcher;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

public class TestVarArgMatcher {

    @Test
    public void testAnyVarArg() {
        Collaborator c = mock(Collaborator.class);
        when(c.f(any())).thenReturn(6);
        assertEquals(6, c.f("a", "b", "c")); // passes
    }

    @Test
    public void testVarArg() {
        Collaborator c = mock(Collaborator.class);

        ArrayElementMatcher<String> matcher = new ArrayElementMatcher<>("b");
        when(c.f(argThat(matcher))).thenAnswer(invocationOnMock -> matcher.isElementFound() ? 7 : 0);

        assertEquals(7, c.f("a", "b", "c")); 
    }


    interface Collaborator {
        int f(String... args);
    }

    private static class ArrayElementMatcher<T> implements ArgumentMatcher<T>, VarargMatcher {
        private final T element;
        private boolean elementFound = false;

        public ArrayElementMatcher(T element) {
            this.element = element;
        }

        public boolean isElementFound() {
            return elementFound;
        }

        @Override
        public boolean matches(T t) {
            elementFound |= element.equals(t);
            return true;
        }
    }
}

The ArrayElementMatcher always returns true for a single match, otherwise Mockito would abort the evaluation, but internally the information is stored if the desired element was encountered. When Mockito has finished matching the arguments - and this match will be true - then the lambda passed into thenAnswer is called and this returns 7 if the given element was found, or 0 otherwise.

Two things to keep in mind:

  1. you always need a new ArrayElementMatcher for every tested call - or add a reset method to the class.

  2. you cannot have more than one when(c.f((argThat(matcher))) defintions in one test method with different matchers, because only one of them would be evaluated.

Edit/Addition:

Just played around a little more and came up with this variation - just showing the Matcher class and the test method:

@Test
public void testVarAnyArg() {
    Collaborator c = mock(Collaborator.class);

    VarargAnyMatcher<String, Integer> matcher = 
            new VarargAnyMatcher<>("b"::equals, 7, 0);
    when(c.f(argThat(matcher))).thenAnswer(matcher);

    assertEquals(7, c.f("a", "b", "c"));
}

private static class VarargAnyMatcher<T, R> implements ArgumentMatcher<T>, VarargMatcher, Answer<R> {
    private final Function<T, Boolean> match;
    private final R success;
    private final R failure;
    private boolean anyMatched = false;

    public VarargAnyMatcher(Function<T, Boolean> match, R success, R failure) {
        this.match = match;
        this.success = success;
        this.failure = failure;
    }

    @Override
    public boolean matches(T t) {
        anyMatched |= match.apply(t);
        return true;
    }

    @Override
    public R answer(InvocationOnMock invocationOnMock) {
        return anyMatched ? success : failure;
    }
}

It's basically the same, but I moved the implementation of the Answer interface into the matcher and extracted the logic to compare the vararg elements into a lambda that is passed in to the matcher ("b"::equals").

That makes the Matcher a little more complex, but the usage of it is much simpler.