1

I want to mock a class which has a varargs parameter method. Based on is there Mockito eq matcher for varargs array? I came up with the following but the ArgumentMatcher is not called at all.

My class to mock:

public class ProcessUtil {
  public Result execute(String... commandParts) throws Exception { ... }
}

My class to test (InstallService) executes a couple of ProcessUtil.execute() and I want to return different Results based on the varargs of the call. Therefor I created this ArgumentMatcher:

class StringVarArgsMatcher implements ArgumentMatcher<String[]>,VarargMatcher {

    private String[] expectedValues;

    StringVarArgsMatcher(String... expectedValues) {
        this.expectedValues = expectedValues;
    }

    @Override
    public boolean matches(String[] arguments) {
        boolean matched = false;
        for(int i = 0; i < expectedValues.length; i++) {
            matched = "".equals(expectedValues[i]) || arguments[i].endsWith(expectedValues[i]);
        }

        return matched;
    }
}

My test is constructed this way:

@Test
public void test() throws Exception{
    ProcessUtil processUtilMock = mock(ProcessUtil.class);
    ProcessUtil.Result installCommandResult = new ProcessUtil.Result(Collections.emptyList(), Collections.emptyList());
    when(processUtilMock.execute(argThat(new StringVarArgsMatcher(new String[]{"", "", "", "", "--install"})))).thenReturn(installCommandResult);

    InstallService installService = new InstallService(processUtilMock);
    boolean databaseInstalled = installService.installDatabase();
    Assert.assertFalse(databaseInstalled);
}

When I run my test it seems that the ArgumentMatcher is not called at all. If I set any breakpoint inside of the matcher execution will not stop. My InstallService will also get a NullPointer-Exception when it tries to evaluate the Result of ProcessUtil.execute()

What is it that I am missing?

Martin
  • 852
  • 7
  • 20
  • I deleted my old workaround and added the solution for your problem. Its easier than I thought it would be, but for that you have to understand how mockito actually applies the matcher ;) – second Nov 22 '19 at 11:08

2 Answers2

1

Change your StringVarArgsMatcher to implement ArgumentMatcher<String> instead of the string array.

When mockito identifies the matcher as a varargs matcher (by relying on the VarargMatcher interface), it matches each argument individually against your matcher.

You will have to adjust your matcher for this, for example:

class StringVarArgsMatcher implements ArgumentMatcher<String>,VarargMatcher {

    private String[] expectedValues;
    private int count = 0;

    StringVarArgsMatcher(String... expectedValues) {
        this.expectedValues = expectedValues;
    }

    @Override
    public boolean matches(String argument) {

        if (count >= expectedValues.length) {
            return false;
        }

        boolean result = "".equals(expectedValues[count]) || argument.endsWith(expectedValues[count]);
        count++;
        return result; 
    }
}
second
  • 4,069
  • 2
  • 9
  • 24
0

The answer from second correctly answers the question about varargs and ArgumentMatcher, but I realized that for my described problem it's the wrong approach. The culprit is this:

My class to test (InstallService) executes a couple of ProcessUtil.execute() and I want to return different Results

Chaining thenReturn() in combination with ArgumentMatcher may help but is kind of inflexible when the internals of InstallService change in the future.

My solution uses thenAnswer().

when(processUtilMock.execute(any())).thenAnswer( invocation -> {
    Object[] arguments = invocation.getArguments();

    ProcessUtil.Result result = null;
    String lastArgument = (String) arguments[arguments.length-1];
    switch (lastArgument) {
        case "1":
            result = new ProcessUtil.Result(Collections.singletonList("1"), Collections.emptyList());
            break;
        case "2":
            result = new ProcessUtil.Result(Collections.emptyList(), Collections.singletonList("2"));
            break;
        case "5":
            result = new ProcessUtil.Result(Collections.singletonList("5"), Collections.emptyList());
            break;
    }

    return result;
});
Martin
  • 852
  • 7
  • 20