12

I've started using the Mockito AdditionalAnswers#returnsFirstArg, which is great:

when(myMock.myFunction(anyString())).then(returnsFirstArg());

but I was wondering if there is an easy way to extract the input argument in order to be used for example in a constructor like:

when(myMock.myFunction(anyString())).thenReturn(new MyObject((String)returnsFirstArg()));

(which obviously doesn't work...)

Mureinik
  • 297,002
  • 52
  • 306
  • 350
Gep
  • 848
  • 13
  • 29

3 Answers3

11

The easiest (only?) approach, IMHO, would be to use the thenAnswer method, which allows you to not only return a value, but actually execute some code. Java 8 makes this particularly elegant, as you could just use an anonymous lambda:

when(myMock.myFunction(anyString()))
    .thenAnswer(i -> new MyObject((String)i.getArguments()[0]);
Mureinik
  • 297,002
  • 52
  • 306
  • 350
5

I think you maybe need a helper class AnswerPipeline that I will introduce it a moment how to make the test more readable, more expressiveness and more interesting!!!

Note: you can transform any Answer to AnswerPipeline by AnswerPipeline#will(Answer) method, not only returnsFirstArg().

THEN using syntax sugar to describe the test, for example:

Function<String, String> function = mock(Function.class);

when(function.apply(anyString())).then(
    /**/ will(returnsFirstArg()) // adapt an Answer to an AnswerPipeline
    /**/.as(String.class)  // set the result type
    /**/.to(String::toUpperCase) // transforming the result
);

assertThat(function.apply("first"), equalTo("FIRST"));

AND then it is easy to solving your problem with no difficulty:

when(myMock.myFunction(anyString()))
           .then(will(returnsFirstArg()).as(String.class).to(MyObject::new));

AnswerPipeline class

interface AnswerPipeline<T> extends Answer<T> {

    static <R> AnswerPipeline<R> will(Answer<R> answer) {
        return answer::answer;
    }

    default <R> AnswerPipeline<R> as(Class<R> type) {
        return to(type::cast);
    }

    default <R> AnswerPipeline<R> to(Function<T, R> mapper) {
        return it -> mapper.apply(answer(it));
    }
}
holi-java
  • 29,655
  • 7
  • 72
  • 83
2

You can use thenAnswer method and create an Answer to get the argument:

when(myMock.myFunction(anyString())).thenAnswer(new Answer<MyObject>() {
    @Override
    public MyObject answer(InvocationOnMock invocation) throws Throwable {
        String s = invocation.getArgument(0); // get first argument
        return new MyObject(s);
    }
});

If you're using java 8, you can use lambda syntax:

when(myMock.myFunction(anyString()))
  .thenAnswer(args -> new MyObject(args.getArgument(0)));

Notes:

  • I didn't need to cast invocation.getArgument(0) to String, but depending on your java/mockito version, maybe it'll be necessary: (String) invocation.getArgument(0)
  • depending on your mockito version, the getArgument(int) method might not exist and you should use getArgumentAt(int, Class) instead (in this case, the call would be getArgumentAt(0, String.class)). Or you can use getArguments()[0] and cast it to String