8

I have a problem when trying to match an array that is passed as a parameter to a method that receives a varargs array.

The anyVararg() matcher that is mentioned in other questions/answers doesn't work for me because I want to make sure the provided array is the one I need.

I reduced the problem to this example which is easier to understand and abstracts the problem (my real issue is production code and has busines logic so it would be confusing for the purpose of this question):

@RunWith(MockitoJUnitRunner.class)
public class UnitTest {
    private Object[] objectArray;
    private List<Object> expected;
    private TestTarget target;

    @Before
    public void setUp() {
        objectArray = new Object[]{ new Object() };
        expected = Arrays.asList(new Object(), new Object());
        target = Mockito.spy(new TestTarget());
    }

    @Test
    public void testMakeList() { // this pass as eq works well with normal array
        doReturn(expected).when(target).toList(Mockito.eq(objectArray));
        Assert.assertEquals(expected, target.makeList(objectArray));
    }

    @Test
    public void testMakeList1() { // this one fails as eq is not working with varargs
        doReturn(expected).when(target).toList1(Mockito.eq(objectArray));
        Assert.assertEquals(expected, target.makeList1(objectArray));
    }

    @Test
    public void testMakeListWithAryEq() { // fails, aryEq is not working with varargs
        doReturn(expected).when(target).toList1(AdditionalMatchers.aryEq(objectArray));
        Assert.assertEquals(expected, target.makeList1(objectArray));
    }

    private class TestTarget {
        public List<Object> makeList(Object[] objects) {
            return toList(objects);
        }

        public List<Object> makeList1(Object[] objects) {
            return toList1(objects);
        }

        protected List<Object> toList(Object[] objs) {
            return null;  // Not implemented "Intentionally"
        }

        protected List<Object> toList1(Object... objs) {
            return null;  // Not implemented "Intentionally"
        }
    }
}

When I run the test cases in the class, the first test case will pass but not the other two, neither using eq nor using aryEq. Showing the following trace:

java.lang.AssertionError: expected:<[java.lang.Object@56d5e457, java.lang.Object@7482384a]> but was:<null>
    at org.junit.Assert.fail(Assert.java:88)
    at org.junit.Assert.failNotEquals(Assert.java:743)
    at org.junit.Assert.assertEquals(Assert.java:118)
    at org.junit.Assert.assertEquals(Assert.java:144)
    ...

This happens because the eq matcher is not working with varargs arrays, is there any alternative to the eq matcher for this use case?

raspacorp
  • 5,037
  • 11
  • 39
  • 51

2 Answers2

7

Ok, I think the answer here requires a custom built matcher, which can be implemented in your unit test as so:

private class MyVarargMatcher extends ArgumentMatcher<Object[]> implements VarargMatcher {
    private Object[] expectedValues;

    MyVarargMatcher(Object... expectedValues) {
        this.expectedValues = expectedValues;
    }

    @Override
    public boolean matches(Object varargArgument) {
        return new EqualsBuilder()
        .append(expectedValues, varargArgument)
        .isEquals();
    }
}

Then, in testMakeList1() change the first line to this:

Mockito.doReturn(expected).when(target).toList1(Mockito.argThat(new MyVarargMatcher(objectArray)));

Sources:
How to properly match varargs in Mockito
http://maciejmadej.blogspot.com/2011/11/capturing-varargs-argument-using-custom.html

Community
  • 1
  • 1
jlewkovich
  • 2,725
  • 2
  • 35
  • 49
  • As I mentioned in the question, the code shown is an example that makes easy to see how the matcher is not working with varargs. The code in which I have issue is property of my company and has production style code with business logic which I cannot share. The question is if there is an equivalent of the matcher that will work with varargs – raspacorp Jun 19 '14 at 18:19
  • 1
    Ok I've created a custom matcher for you to use, please let me know if that works. – jlewkovich Jun 20 '14 at 14:34
  • 1
    It seems that the answer is: no there are not currently eq equivalent matchers in Mockito for varargs, but we can create one like the one you and the blog entry that you refer posted. I implemented mine and also added a method that extracts the matcher call so I can just write eqVararg(objectArray) like this: private Object[] eqVararg(Object... vararg) { return Mockito.argThat(new VarargsMatcher(vararg)); } – raspacorp Jun 20 '14 at 15:47
  • I've ported your custom matcher to Scala. More details here: https://www.anchormen.nl/variable-argument-matching-in-scala-with-mockito-unit-testing-spark-drivers/ – Jeroen Vlek Oct 08 '15 at 07:08
0

This is no problem with matching varargs. The only limitation is that you have to specify each individual array entry as a matched argument. I have updated your code below to show what I mean. I created a second objectArray2 to make the point clearer. All tests pass:

@RunWith(MockitoJUnitRunner.class)
public class UnitTest {
    private Object[] objectArray;
    private Object[] objectArray2;
    private List<Object> expected;
    private TestTarget target;
    private Object obj,obj2;

    @Before
    public void setUp() {
        obj = new Object();
        obj2 = new Object();
        objectArray = new Object[]{ obj };
        objectArray2 = new Object[]{ obj, obj2 };
        expected = Arrays.asList(new Object(), new Object());
        target = Mockito.spy(new TestTarget());
    }

    @Test
    public void testMakeList() { // this pass as eq works well with normal array
        doReturn(expected).when(target).toList(Mockito.eq(objectArray));
        Assert.assertEquals(expected, target.makeList(objectArray));
    }

    @Test
    public void testMakeList1() { // since objectArray has one entry you need to add one matching argument
        doReturn(expected).when(target).toList1(Mockito.eq(obj));
        Assert.assertEquals(expected, target.makeList1(objectArray));
    }

    @Test
    public void testMakeListWithAryEq() { // since objectArray2 has two entries you need to add two matching arguments
        doReturn(expected).when(target).toList1(Mockito.eq(obj),Mockito.eq(obj2));
        Assert.assertEquals(expected, target.makeList1(objectArray2));
    }

    private class TestTarget {
        public List<Object> makeList(Object[] objects) {
            return toList(objects);
        }

        public List<Object> makeList1(Object[] objects) {
            return toList1(objects);
        }

        protected List<Object> toList(Object[] objs) {
            return null;  // Not implemented "Intentionally"
        }

        protected List<Object> toList1(Object... objs) {
            return null;  // Not implemented "Intentionally"
        }
    }
}
Gareth
  • 936
  • 6
  • 14