diff --git a/src/main/java/org/mockito/ArgumentMatchers.java b/src/main/java/org/mockito/ArgumentMatchers.java index 4a6c84df4d..3734e71653 100644 --- a/src/main/java/org/mockito/ArgumentMatchers.java +++ b/src/main/java/org/mockito/ArgumentMatchers.java @@ -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; @@ -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; @@ -1317,6 +1320,24 @@ public static double doubleThat(ArgumentMatcher 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 the type of the vararg or array of varargs + * @return {@code null} + */ + public static T varargsAsArray(T value) { + ArgumentMatcher nestedMatcher = mockingProgress().getArgumentMatcherStorage().popMatcher(); + reportMatcher(new TreatVarargsAsArray(nestedMatcher)); + return null; + } + private static void reportMatcher(ArgumentMatcher matcher) { mockingProgress().getArgumentMatcherStorage().reportMatcher(matcher); } diff --git a/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java b/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java index e085c25a32..f21327ee86 100644 --- a/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java +++ b/src/main/java/org/mockito/internal/invocation/MatcherApplicationStrategy.java @@ -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; @@ -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]; @@ -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> matchers) { - return lastMatcher(matchers) instanceof VarargMatcher; - } - private static List> appendLastMatcherNTimes(List> matchers, int timesToAppendLastMatcher) { ArgumentMatcher lastMatcher = lastMatcher(matchers); @@ -127,6 +143,18 @@ private static ArgumentMatcher lastMatcher(List> 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(); + } } } diff --git a/src/main/java/org/mockito/internal/matchers/TreatVarargsAsArray.java b/src/main/java/org/mockito/internal/matchers/TreatVarargsAsArray.java new file mode 100644 index 0000000000..eae9dcef35 --- /dev/null +++ b/src/main/java/org/mockito/internal/matchers/TreatVarargsAsArray.java @@ -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 + implements ArgumentMatcher, ContainsExtraTypeInfo, CapturesArguments, Serializable { + + private final ArgumentMatcher delegate; + + public TreatVarargsAsArray(ArgumentMatcher 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; + } +} diff --git a/src/main/java/org/mockito/internal/matchers/text/MatcherToString.java b/src/main/java/org/mockito/internal/matchers/text/MatcherToString.java index f67b8688fc..fc0b7cee6f 100644 --- a/src/main/java/org/mockito/internal/matchers/text/MatcherToString.java +++ b/src/main/java/org/mockito/internal/matchers/text/MatcherToString.java @@ -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. @@ -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(); diff --git a/src/main/java/org/mockito/internal/progress/ArgumentMatcherStorage.java b/src/main/java/org/mockito/internal/progress/ArgumentMatcherStorage.java index 572b9d41a4..96ea27b602 100644 --- a/src/main/java/org/mockito/internal/progress/ArgumentMatcherStorage.java +++ b/src/main/java/org/mockito/internal/progress/ArgumentMatcherStorage.java @@ -26,4 +26,5 @@ public interface ArgumentMatcherStorage { void reset(); + ArgumentMatcher popMatcher(); } diff --git a/src/main/java/org/mockito/internal/progress/ArgumentMatcherStorageImpl.java b/src/main/java/org/mockito/internal/progress/ArgumentMatcherStorageImpl.java index 6e8dd24c44..2a66084ff9 100644 --- a/src/main/java/org/mockito/internal/progress/ArgumentMatcherStorageImpl.java +++ b/src/main/java/org/mockito/internal/progress/ArgumentMatcherStorageImpl.java @@ -84,7 +84,8 @@ private void assertStateFor(String additionalMatcherName, int subMatchersCount) } } - private ArgumentMatcher popMatcher() { + @Override + public ArgumentMatcher popMatcher() { return matcherStack.pop().getMatcher(); } diff --git a/src/test/java/org/mockitousage/matchers/VarargsTest.java b/src/test/java/org/mockitousage/matchers/VarargsTest.java index 685c918a12..d4ab412d1f 100644 --- a/src/test/java/org/mockitousage/matchers/VarargsTest.java +++ b/src/test/java/org/mockitousage/matchers/VarargsTest.java @@ -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; @@ -303,26 +305,51 @@ public void shouldNotCaptureVarArgs_1args2captures() { } - /** - * As of v2.0.0-beta.118 this test fails. Once the github issues: - *
    - *
  • '#584 ArgumentCaptor can't capture varargs-arrays - *
  • #565 ArgumentCaptor should be type aware' are fixed this test must - * succeed - *
- */ @Test - @Ignore("Blocked by github issue: #584 & #565") - public void shouldCaptureVarArgsAsArray() { - mock.varargs("1", "2"); + public void shouldCaptureVarArgsAsArray_noVarargs() { + mock.varargs("1"); ArgumentCaptor 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 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 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 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"); diff --git a/src/test/java/org/mockitousage/verification/BasicVerificationTest.java b/src/test/java/org/mockitousage/verification/BasicVerificationTest.java index aa350cfb9e..a7d13f8641 100644 --- a/src/test/java/org/mockitousage/verification/BasicVerificationTest.java +++ b/src/test/java/org/mockitousage/verification/BasicVerificationTest.java @@ -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"}))); + } }