How to properly match varargs in Mockito

0 votes
asked Apr 13, 2010 by qualidafial

I've been trying to get to mock a method with vararg parameters using Mockito:

interface A {
  B b(int x, int y, C... c);
}

A a = mock(A.class);
B b = mock(B.class);

when(a.b(anyInt(), anyInt(), any(C[].class))).thenReturn(b);
assertEquals(b, a.b(1, 2));

This doesn't work, however if I do this instead:

when(a.b(anyInt(), anyInt())).thenReturn(b);
assertEquals(b, a.b(1, 2));

This works, despite that I have completely omitted the varargs argument when stubbing the method.

Any clues?

7 Answers

0 votes
answered Apr 14, 2010 by topchef

Mockito 1.8.1 introduced anyVararg() matcher:

when(a.b(anyInt(), anyInt(), Matchers.<String>anyVararg())).thenReturn(b);

Also see history for this: https://code.google.com/archive/p/mockito/issues/62

0 votes
answered Apr 3, 2011 by eli-levine

A somewhat undocumented feature: If you want to develop a custom Matcher that matches vararg arguments you need to have it implement org.mockito.internal.matchers.VarargMatcher for it to work correctly. It's an empty marker interface, without which Mockito will not correctly compare arguments when invoking a method with varargs using your Matcher.

For example:

class MyVarargMatcher extends ArgumentMatcher<C[]> implements VarargMatcher {
    @Override public boolean matches(Object varargArgument) {
        return /* does it match? */ true;
    }
}

when(a.b(anyInt(), anyInt(), argThat(new MyVarargMatcher()))).thenReturn(b);
0 votes
answered Apr 30, 2015 by peter-westmacott

Building on Eli Levine's answer here is a more generic solution:

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.VarargMatcher;

import static org.mockito.Matchers.argThat;

public class VarArgMatcher<T> extends ArgumentMatcher<T[]> implements VarargMatcher {

    public static <T> T[] varArgThat(Matcher<T[]> hamcrestMatcher) {
        argThat(new VarArgMatcher(hamcrestMatcher));
        return null;
    }

    private final Matcher<T[]> hamcrestMatcher;

    private VarArgMatcher(Matcher<T[]> hamcrestMatcher) {
        this.hamcrestMatcher = hamcrestMatcher;
    }

    @Override
    public boolean matches(Object o) {
        return hamcrestMatcher.matches(o);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("has varargs: ").appendDescriptionOf(hamcrestMatcher);
    }

}

Then you can use it with hamcrest's array matchers thus:

verify(a).b(VarArgMatcher.varArgThat(
            org.hamcrest.collection.IsArrayContaining.hasItemInArray("Test")));

(Obviously static imports will render this more readable.)

0 votes
answered Jan 3, 2016 by seyed-jalal-hosseini

In my case the signature of the method that I want to capture its argument is:

    public byte[] write(byte ... data) throws IOException;

In this case you should cast to byte array explicitly:

       when(spi.write((byte[])anyVararg())).thenReturn(someValue);

I'm using mockito version 1.10.19

0 votes
answered Jan 14, 2016 by mark

I have been using the code in Peter Westmacott's answer however with Mockito 2.2.15 you can now do the following:

verify(a).method(100L, arg1, arg2, arg3)

where arg1, arg2, arg3 are varargs.

0 votes
answered Jan 29, 2016 by npike

Building on topchef's answer,

For 2.0.31-beta I had to use Mockito.anyVararg instead of Matchers.anyVararrg:

when(a.b(anyInt(), anyInt(), Mockito.<String>anyVararg())).thenReturn(b);
0 votes
answered Sep 15, 2017 by richard-whitehead

You can also loop over the arguments:

Object[] args = invocation.getArguments(); 
for( int argNo = 0; argNo < args.length; ++argNo) { 
    // ... do something with args[argNo] 
}

for example check their types and cast them appropriately, add to a list or whatever.

Welcome to Q&A, where you can ask questions and receive answers from other members of the community.
Website Online Counter

...