3

I'd like to stub some code so that a vararg method returns true when one of the arguments matches a specific value. For example, given the existing code that I can't change:

(Using Kotlin here, but I figure this applies to any Java situation.)

class Foo {
    fun bar(vararg strings : String) : Boolean {
        // Iterates `strings` and returns true when one satisfies some criteria
    }
}

... I want to write stub code similar to this:

val foo = Foo()
whenever(foo.bar(eq("AAA"))).thenReturn(true)

This works fine when the call is exactly foo.bar("AAA").

However, there are times when the code under test makes the call foo.bar("AAA", "BBB"), and in those cases, it fails.

How can I revise my stub code so it works when any number of varargs are passed in the call?

Edit Flagged as a possible duplicate; in that case, the scenario contemplates the complete omission of the varargs in the call. Here, I'm trying to match one specific element of the varargs array.

Hugh
  • 1,133
  • 1
  • 12
  • 27

2 Answers2

4

You have to stub your method 2 times. First the least specific stub:

val foo = Foo()
whenever(foo.bar(any())).thenReturn(false) // or whatever you want to return/throw here

And then the more specific single argument method:

whenever(foo.bar(eq("AAA"))).thenReturn(true)

After your comment you may aswell use something like this (using Java this time):

when(foo.bar(any())).thenAnswer(invocation -> {
    for (Object argument : invocation.getArguments()) {
        if ("AAA".equals(argument)) return true;
    }
    return false;
});

And the same in Kotlin

whenever(foo.bar(any()).thenAnswer {
    it.arguments.contains("AAA")
}
Lino
  • 19,604
  • 6
  • 47
  • 65
  • 2
    Thanks! However, this won't work for my situation. As long as "AAA" is one of the args, it should return true. It doesn't have to be the one and only argument in the call. – Hugh Mar 18 '19 at 13:14
  • 1
    @Hugh see my edit, you can just always iterate over the arguments and then check if `"AAA"` was one of them – Lino Mar 18 '19 at 13:17
  • 1
    Thanks @Lino! Works perfectly. I have an edit pending peer review that adds the Kotlin version of your solution. – Hugh Mar 18 '19 at 13:36
  • 1
    @Hugh I approved your edit, but removed your "Thanks" comments, as you should not put that in the answer. But glad that I could help you :) – Lino Mar 18 '19 at 13:42
2

You can create your own matcher :

public class MyVarargMatcher extends ArgumentMatcher<String[]> implements VarargMatcher
{
    private String expectedFirstValue;

    public MyVarargMatcher(String expectedFirstValue)
    {
        this.expectedFirstValue = expectedFirstValue;
    }

    @Override
    public boolean matches(Object varargArgument) {
        if (varargArgument instanceof String[])
        {
            String[] args = (String[]) varargArgument;
            return Stream.of(args).anyMatch(expectedFirstValue::equals);
        }
        return false;
    }
}

And then use it like that (Java code) :

Foo foo = Mockito.mock(Foo.class);
Mockito.doReturn(true).when(foo).bar(Mockito.argThat(new MyVarargMatcher("AAA")));

edited with the op's comment : As long as "AAA" is one of the args, it should return true

ToYonos
  • 16,469
  • 2
  • 54
  • 70