Skip to content

Instantly share code, notes, and snippets.

@lpenaud
Created November 14, 2024 13:42
Show Gist options
  • Save lpenaud/8bfeb9a3eb2b610d799f3c9fa6b4e02c to your computer and use it in GitHub Desktop.
Save lpenaud/8bfeb9a3eb2b610d799f3c9fa6b4e02c to your computer and use it in GitHub Desktop.
Multi-Answer Mockito
import java.util.LinkedList;
import java.util.List;
import org.mockito.ArgumentMatcher;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import lombok.AllArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* Helpers class to match multi calls to the same method.
* @param <T> Argument type.
* @param <R> Result type.
*/
@Accessors(fluent = true)
public class ArrayMatcherAnswer<T, R> implements Answer<R>, ArgumentMatcher<T> {
@AllArgsConstructor
private static class AnswerMock<T, R> {
private final ArgumentMatcher<T> matcher;
private final R result;
}
private final List<AnswerMock<T, R>> array = new LinkedList<>();
@Setter
private R defaultResult;
private AnswerMock<T, R> currentMock;
/**
* Add an argument matcher with the attached result.
* @param matcher Argument matcher.
* @param result Result to return.
* @return this
*/
public ArrayMatcherAnswer<T, R> add(final ArgumentMatcher<T> matcher, final R result) {
this.array.add(new AnswerMock<>(matcher, result));
return this;
}
/**
* Add an argument matcher with the default result.
* @param matcher Argument matcher.
* @return this
*/
public ArrayMatcherAnswer<T, R> add(final ArgumentMatcher<T> matcher) {
return this.add(matcher, this.defaultResult);
}
/**
* Add an argument with the default result.
* Use {@linkplain Object#equals(Object)} as {@linkplain ArgumentMatcher}.
* @param object Argument
* @return this
*/
public ArrayMatcherAnswer<T, R> add(final T object) {
return this.add(object, this.defaultResult);
}
/**
* Add an argument with the given result.
* Use {@linkplain Object#equals(Object)} as {@linkplain ArgumentMatcher}.
* @param object Argument
* @param result Result to return.
* @return this.
*/
public ArrayMatcherAnswer<T, R> add(final T object, final R result) {
return this.add(object::equals, result);
}
@Override
public R answer(final InvocationOnMock invocation) throws Throwable {
return this.currentMock.result;
}
@Override
public boolean matches(final T argument) {
this.currentMock = this.array.stream()
.filter(a -> a.matcher.matches(argument))
.findAny()
.orElse(null);
return this.currentMock != null && this.array.remove(this.currentMock);
}
}

ArrayMatcherAnswer

Simple example:

final var expected = List.of("yes", "no");
final var matchers = new ArrayMatcherAnswer<Integer, String>()
                        .defaultResult("yes")
                        .add(1)
                        .add(2, "no");
Mockito.when(this.subService.jane(ArgumentMatchers.argThat(matchers))))
    .thenAnswer(matchers);
final var results = this.service.doe();
Assertions.assertEquals(expected, results);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment