Skip to content

Commit

Permalink
Fix issue mockito#1222 and mockito#584
Browse files Browse the repository at this point in the history
Adds a new method varargsAsArray(...) that indicates that the varargs
should be matched/captured as a single array rather than separate
values.
  • Loading branch information
paulduffin committed Oct 26, 2017
1 parent e10c540 commit 590eed7
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 25 deletions.
21 changes: 21 additions & 0 deletions src/main/java/org/mockito/ArgumentMatchers.java
Expand Up @@ -6,12 +6,14 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
import org.mockito.internal.matchers.Any;
import org.mockito.internal.matchers.Contains;
import org.mockito.internal.matchers.EndsWith;
Expand All @@ -22,6 +24,7 @@
import org.mockito.internal.matchers.Null;
import org.mockito.internal.matchers.Same;
import org.mockito.internal.matchers.StartsWith;
import org.mockito.internal.matchers.TreatVarargsAsArray;
import org.mockito.internal.matchers.apachecommons.ReflectionEquals;
import org.mockito.internal.util.Primitives;

Expand Down Expand Up @@ -1317,6 +1320,24 @@ public static double doubleThat(ArgumentMatcher<Double> matcher) {
return 0;
}

/**
* Indicates that the varargs should be matched as an array rather than individual values.
*
* The value must be specified as a call to a method for matching an argument, e.g.
* {@link #eq(Object)} or {@link ArgumentCaptor#capture()}. Failure to do so will result in
* either an {@link EmptyStackException} or an {@link InvalidUseOfMatchersException}.
*
* @param value expected to be a matcher such as {@code eq(...) or
* {@code captor.capture()}
* @param <T> the type of the vararg or array of varargs
* @return {@code null}
*/
public static <T> T varargsAsArray(T value) {
ArgumentMatcher nestedMatcher = mockingProgress().getArgumentMatcherStorage().popMatcher();
reportMatcher(new TreatVarargsAsArray<Object>(nestedMatcher));
return null;
}

private static void reportMatcher(ArgumentMatcher<?> matcher) {
mockingProgress().getArgumentMatcherStorage().reportMatcher(matcher);
}
Expand Down
Expand Up @@ -6,13 +6,15 @@

import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS;
import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.MATCH_EACH_VARARGS_WITH_LAST_MATCHER;
import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.ONE_MATCHER_PER_ARGUMENT;
import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.ONE_MATCHER_PER_EXPANDED_ARGUMENT;
import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.ONE_MATCHER_PER_RAW_ARGUMENT;

import java.util.ArrayList;
import java.util.List;

import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.CapturingMatcher;
import org.mockito.internal.matchers.TreatVarargsAsArray;
import org.mockito.internal.matchers.VarargMatcher;
import org.mockito.invocation.Invocation;

Expand Down Expand Up @@ -74,7 +76,7 @@ public boolean forEachMatcherAndArgument(ArgumentMatcherAction action) {
if (matchingType == ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS)
return false;

Object[] arguments = invocation.getArguments();
Object[] arguments = matchingType.getArguments(invocation);
for (int i = 0; i < arguments.length; i++) {
ArgumentMatcher<?> matcher = matchers.get(i);
Object argument = arguments[i];
Expand All @@ -91,21 +93,35 @@ private static MatcherApplicationType getMatcherApplicationType(Invocation invoc
final int expandedArguments = invocation.getArguments().length;
final int matcherCount = matchers.size();

boolean treatVarargsAsArray = false;
boolean reuseLastMatcherForVarargs = false;
if (matcherCount > 0 && invocation.getMethod().isVarArgs()) {
ArgumentMatcher<?> lastMatcher = lastMatcher(matchers);
if (lastMatcher instanceof TreatVarargsAsArray) {
treatVarargsAsArray = true;
} else if (lastMatcher instanceof VarargMatcher) {
reuseLastMatcherForVarargs = true;
}
}

if (expandedArguments == matcherCount) {
return ONE_MATCHER_PER_ARGUMENT;
return treatVarargsAsArray
? ONE_MATCHER_PER_RAW_ARGUMENT : ONE_MATCHER_PER_EXPANDED_ARGUMENT;
}

if (rawArguments == matcherCount && isLastMatcherVargargMatcher(matchers)) {
return MATCH_EACH_VARARGS_WITH_LAST_MATCHER;
if (rawArguments == matcherCount) {
if (treatVarargsAsArray) {
return ONE_MATCHER_PER_RAW_ARGUMENT;
}

if (reuseLastMatcherForVarargs) {
return MATCH_EACH_VARARGS_WITH_LAST_MATCHER;
}
}

return ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS;
}

private static boolean isLastMatcherVargargMatcher(final List<ArgumentMatcher<?>> matchers) {
return lastMatcher(matchers) instanceof VarargMatcher;
}

private static List<ArgumentMatcher<?>> appendLastMatcherNTimes(List<ArgumentMatcher<?>> matchers, int timesToAppendLastMatcher) {
ArgumentMatcher<?> lastMatcher = lastMatcher(matchers);

Expand All @@ -127,6 +143,18 @@ private static ArgumentMatcher<?> lastMatcher(List<ArgumentMatcher<?>> matchers)
}

enum MatcherApplicationType {
ONE_MATCHER_PER_ARGUMENT, MATCH_EACH_VARARGS_WITH_LAST_MATCHER, ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS;
ONE_MATCHER_PER_EXPANDED_ARGUMENT,
ONE_MATCHER_PER_RAW_ARGUMENT() {
@Override
Object[] getArguments(Invocation invocation) {
return invocation.getRawArguments();
}
},
MATCH_EACH_VARARGS_WITH_LAST_MATCHER,
ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS;

Object[] getArguments(Invocation invocation) {
return invocation.getArguments();
}
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2017 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.matchers;

import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.text.MatcherToString;
import java.io.Serializable;

/**
*/
public class TreatVarargsAsArray<T>
implements ArgumentMatcher<T>, ContainsExtraTypeInfo, CapturesArguments, Serializable {

private final ArgumentMatcher<T> delegate;

public TreatVarargsAsArray(ArgumentMatcher<T> delegate) {
this.delegate = delegate;
}

@Override
public boolean matches(T argument) {
return delegate.matches(argument);
}

@Override
public void captureFrom(Object argument) {
if (delegate instanceof CapturesArguments) {
((CapturesArguments) delegate).captureFrom(argument);
}
}

@Override
public String toStringWithType() {
if (delegate instanceof ContainsExtraTypeInfo) {
return ((ContainsExtraTypeInfo) delegate).toStringWithType();
}
return toString();
}

@Override
public String toString() {
return "varargsAsArray(" + MatcherToString.toString(delegate) + ")";
}

@Override
public boolean typeMatches(Object target) {
return false;
}
}
Expand Up @@ -13,7 +13,7 @@
/**
* Provides better toString() text for matcher that don't have toString() method declared.
*/
class MatcherToString {
public class MatcherToString {

/**
* Attempts to provide more descriptive toString() for given matcher.
Expand All @@ -25,7 +25,7 @@ class MatcherToString {
* @param matcher
* @return
*/
static String toString(ArgumentMatcher<?> matcher) {
public static String toString(ArgumentMatcher<?> matcher) {
Class<?> cls = matcher.getClass();
while(cls != Object.class) {
Method[] methods = cls.getDeclaredMethods();
Expand Down
Expand Up @@ -26,4 +26,5 @@ public interface ArgumentMatcherStorage {

void reset();

ArgumentMatcher<?> popMatcher();
}
Expand Up @@ -84,7 +84,8 @@ private void assertStateFor(String additionalMatcherName, int subMatchersCount)
}
}

private ArgumentMatcher<?> popMatcher() {
@Override
public ArgumentMatcher<?> popMatcher() {
return matcherStack.pop().getMatcher();
}

Expand Down
51 changes: 39 additions & 12 deletions src/test/java/org/mockitousage/matchers/VarargsTest.java
Expand Up @@ -9,6 +9,8 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.varargsAsArray;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -303,26 +305,51 @@ public void shouldNotCaptureVarArgs_1args2captures() {

}

/**
* As of v2.0.0-beta.118 this test fails. Once the github issues:
* <ul>
* <li>'#584 ArgumentCaptor can't capture varargs-arrays
* <li>#565 ArgumentCaptor should be type aware' are fixed this test must
* succeed
* </ul>
*/
@Test
@Ignore("Blocked by github issue: #584 & #565")
public void shouldCaptureVarArgsAsArray() {
mock.varargs("1", "2");
public void shouldCaptureVarArgsAsArray_noVarargs() {
mock.varargs("1");

ArgumentCaptor<String[]> varargCaptor = ArgumentCaptor.forClass(String[].class);

verify(mock).varargs(varargCaptor.capture());
verify(mock).varargs(varargsAsArray(varargCaptor.capture()));

assertThat(varargCaptor).containsExactly(new String[] { "1" });
}

@Test
public void shouldCaptureVarArgsAsArray_oneVararg() {
mock.varargs("1");
mock.varargs("2");

ArgumentCaptor<String[]> varargCaptor = ArgumentCaptor.forClass(String[].class);

verify(mock, times(2)).varargs(varargsAsArray(varargCaptor.capture()));

assertThat(varargCaptor).containsExactly(new String[] { "1" }, new String[] { "2" });
}

@Test
public void shouldCaptureVarArgsAsArray_twoVarargs() {
mock.mixedVarargs(getClass(), "1", "2");

ArgumentCaptor<String[]> varargCaptor = ArgumentCaptor.forClass(String[].class);

verify(mock).mixedVarargs(any(), varargsAsArray(varargCaptor.capture()));

assertThat(varargCaptor).containsExactly(new String[] { "1", "2" });
}

@Test
public void shouldCaptureVarArgsAsByteArray_twoVarargs() {
mock.varargsbyte((byte) 1, (byte) 2);

ArgumentCaptor<byte[]> varargCaptor = ArgumentCaptor.forClass(byte[].class);

verify(mock).varargsbyte(varargsAsArray(varargCaptor.capture()));

assertThat(varargCaptor).containsExactly(new byte[] { (byte) 1, (byte) 2 });
}

@Test
public void shouldNotMatchRegualrAndVaraArgs() {
mock.varargsString(1, "a","b");
Expand Down
Expand Up @@ -115,4 +115,49 @@ public void shouldDetectWhenOverloadedMethodCalled() throws Exception {
fail();
} catch(WantedButNotInvoked e) {}
}

@Test
public void shouldVerifyVarargsAsArray_nullVarargsArray() throws Exception {
IMethods mock = mock(IMethods.class);

mock.mixedVarargs(null, (String[]) null);

verify(mock).mixedVarargs(any(), varargsAsArray(eq((String[]) null)));
}

@Test
public void shouldVerifyVarargsAsArray_nullVararg() throws Exception {
IMethods mock = mock(IMethods.class);

mock.mixedVarargs(null, (String) null);

verify(mock).mixedVarargs(any(), varargsAsArray(eq(new String[] {null})));
}

@Test
public void shouldVerifyVarargsAsArray_noVarargs() throws Exception {
IMethods mock = mock(IMethods.class);

mock.mixedVarargs("1");

verify(mock).mixedVarargs(any(), varargsAsArray(eq(new String[] {})));
}

@Test
public void shouldVerifyVarargsAsArray_singleVararg() throws Exception {
IMethods mock = mock(IMethods.class);

mock.mixedVarargs("1", "2");

verify(mock).mixedVarargs(any(), varargsAsArray(eq(new String[] {"2"})));
}

@Test
public void shouldVerifyVarargsAsArray_twoVarargs() throws Exception {
IMethods mock = mock(IMethods.class);

mock.mixedVarargs("1", "2", "3");

verify(mock).mixedVarargs(any(), varargsAsArray(eq(new String[] {"2", "3"})));
}
}

0 comments on commit 590eed7

Please sign in to comment.