Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue #1222 and #584 #1224

Closed

Conversation

paulduffin
Copy link
Contributor

@paulduffin paulduffin commented Oct 26, 2017

Adds a new method varargsAsArray(...) that indicates that the varargs
should be matched/captured as a single array rather than separate
values.

Fixes #1222
Fixes #584

@codecov-io
Copy link

codecov-io commented Oct 26, 2017

Codecov Report

Merging #1224 into release/2.x will increase coverage by 0.07%.
The diff coverage is 97.67%.

Impacted file tree graph

@@                Coverage Diff                @@
##             release/2.x    #1224      +/-   ##
=================================================
+ Coverage          87.27%   87.34%   +0.07%     
- Complexity          2305     2321      +16     
=================================================
  Files                287      288       +1     
  Lines               5869     5905      +36     
  Branches             703      709       +6     
=================================================
+ Hits                5122     5158      +36     
  Misses               556      556              
  Partials             191      191
Impacted Files Coverage Δ Complexity Δ
.../internal/progress/ArgumentMatcherStorageImpl.java 100% <ø> (ø) 15 <0> (ø) ⬇️
...ockito/internal/matchers/text/MatcherToString.java 90% <0%> (ø) 4 <0> (ø) ⬇️
...nternal/invocation/MatcherApplicationStrategy.java 100% <100%> (ø) 21 <0> (+5) ⬆️
...mockito/internal/matchers/TreatVarargsAsArray.java 100% <100%> (ø) 10 <10> (?)
src/main/java/org/mockito/ArgumentMatchers.java 99.1% <100%> (+0.02%) 60 <1> (+1) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e10c540...be0cd33. Read the comment docs.

Copy link
Contributor

@TimvdLippe TimvdLippe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah cool that you were able to figure it out. Thanks a lot for the investigation and effort to create a fix @paulduffin! The implementation SFTM, but I would like either @mockitoguy or @bric3 to sign off on this logic, as they were involved in earlier changes in this code part.

@paulduffin
Copy link
Contributor Author

I am working on fixing the code coverage problem and adding some more tests for TreatsVarargsAsArray.

@TimvdLippe
Copy link
Contributor

Code coverage seems mostly concerned regarding the boilerplate toString and friends. Not a huge dealbreaker, but adding more tests for to get full coverage is 💯

Adds a new method varargsAsArray(...) that indicates that the varargs
should be matched/captured as a single array rather than separate
values.
@ChristianSchwarz
Copy link
Contributor

ChristianSchwarz commented Oct 26, 2017

I would like to see some test cases too, to get a feel for the API in corner cases

ArgumentCaptor<int[]>  captor;
when(mock).varargInts(varargsAsArray(captor.capture())) //varargInts(int...values)
when(mock).varargInts(varargsAsArray(eq(5))) //varargInts(int...values)
when(mock).stringVarargInts(eq("a"),varargsAsArray(123)) //stringVarargInts(String s,int...values)
when(mock).consume(varargsAsArray(eq("a"))) //consume(String s);
mock.consume("a");
when(mock).consumeInt(varargsAsArray(eq(1))) //consumeInt(int i);
mock.consumeInt(1);

@paulduffin
Copy link
Contributor Author

I did add some more tests after the first upload. Do you want even more?

@ChristianSchwarz
Copy link
Contributor

In the end the mockito-core members or you decide! I am as you a contributor and can only give the advices to add more non-happy-path tests.

Last year I looked into the this issue and other related varargs, capturing and matching issues. Which leaded to some refactorings, regarding the whole capturing and matching process. The MatcherApplicationStrategy were the most of your changes were made are a result of it. I never found a good solution to handle / prevent API misuses. From that POV I would like to know 2 things:

  • How does the API handle/prevent misuses and communicate them?
  • Can we ensure this API solves the problem and introduces no new ones.

Here is some other interesting test case:

  mock.varargsInt(1,2,3);
  verify(mock).varargInts(varargsAsArray(or(eq(null),eq(new int[]{1,2,3})))) 
  //what is the result: success, assertion failure or an exception indicating a bug

@mockitoguy
Copy link
Member

Looking at it!

@ChristianSchwarz
Copy link
Contributor

Somewhere it should be checked that this new matcher is only allowed to be placed as last-arg of a vararg call.

mockitoguy added a commit that referenced this pull request Oct 31, 2017
Not intended for check-in but demonstrates that we can match varargs flexibly.

See #1224

Can we follow down this path and fix problem #1222 and #584 in the way the API is most convenient to use?
@mockitoguy
Copy link
Member

Hey guys! This is a great discussion, thank you @TimvdLippe for bringing me in!

@paulduffin, this PR is conceivable! Before we merge it let's make an attempt to solve the use case more conveniently for our users.

@ChristianSchwarz, can you comment on #1235? I prototyped/hacked the implementation so that the use case is handled by Mockito out of the box. Can you point out gaps / add tests that demonstrate gaps?

PR #1235 makes both types of syntax work (and all existing Mockito tests are passing):

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

//traditional verify with varargs, same number of args
verify(mock).mixedVarargs(any(), eq("2"), eq("3"));

//new matching, using an array
verify(mock).mixedVarargs(any(), eq(new String[] {"2", "3"}));
verify(mock).mixedVarargs(any(), (String[]) any());

Thanks everyone for working on this use case!

@ChristianSchwarz
Copy link
Contributor

ChristianSchwarz commented Oct 31, 2017

@paulduffin Some puzzelz, but they are solvable...

    @Test
    public void shouldNoWorkOnNonVarargParameter_argOfTypeString()   {
        exception.expect(MockitoException.class);
        exception.expectMessage("varargsAsArray(..) is only allowed on vararg parameters");
        when(mock.simpleMethod(varargsAsArray(eq("")))).thenReturn("");
    }

    @Test
    public void shouldNoWorkOnNonVarargParameter_argOfTypeStringArray()   {
        exception.expect(MockitoException.class);
        exception.expectMessage("varargsAsArray(..) is only allowed on vararg parameters, actual parameter is of type: String");
        when(mock.simpleMethod("",varargsAsArray(eq(new String[0])))).thenReturn("");
    }

    @Test
    public void shouldNoWorkOnNonVarargParameter_argOfTypeStringArray2()   {
        exception.expect(MockitoException.class);
        exception.expectMessage("varargsAsArray(..) is only allowed on vararg parameters, actual parameter is of type: String[]");
        when(mock.simpleMethod("",varargsAsArray(new String[0]))).thenReturn("");
    }

    @Test
    public void shouldNotCompile()  {
        when(mock.intArgumentReturningInt(varargsAsArray(0))).thenReturn(1);
    }

    @Test
    public void shouldConvertRawValuesToEqualsMatcher()  {
        when(mock.varargsReturningString(varargsAsArray(0))).thenReturn("");
    }

@paulduffin
Copy link
Contributor Author

They could all cause a compile error as long as the code was compiled with Error Prone and a suitable Error Prone checker was written for it.

Otherwise, the only choice is to have them as runtime errors.

Preventing it being used with non-vararg and non-array parameters could be done by having it return T[] instead of T. Unfortunately, that will not work with primitive varargs. e.g. if the parameter was int... then varargsAsArray(eq(new int[] {1})) would have to return int[] but T[] cannot be assigned to int[] because T cannot be primitive. The closest it can be is Integer and Integer[] is not assignable to int[].

That would require primitive specializations of varargsAsArray, e.g. varargsAsIntArray, etc.

@mockitoguy
Copy link
Member

Thank you for explaining the PR here and in #1235. It is immensely helpful. My feedback:

  • +1 to adding explicit "varargs" matcher
  • what is the best name? What about shorter "vararg"?
  • with the current behavior, there are opportunities to improve usability (if we believe it is important enough, let's put together a ticket):
    • more context to the verification error message when vararg is involved and number of args don't match. We could hint to the new matcher.
    • improve behavior for the use cases we can solve unambiguously.

I'm doing the review of the code now.

Copy link
Member

@mockitoguy mockitoguy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it. Can you consider my feedback? If you can improve the PR using small, separate commits it will be really easy to review the delta.

I suggested improvements but the change is really good! Looking forward to merging!!!

@@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "individual values" -> "individual value"

*
* 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}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a unit test for this behavior? Let's see how the unit test looks like and what is the user experience. If the user experience is not great, can we offer a clean exception describing API misuse?

* {@code captor.capture()}
* @param <T> the type of the vararg or array of varargs
* @return {@code null}
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature is really cool and it deserves new minor version. Can you add "since" tag to the Javadoc with the next minor version? Also, let's update the "version.properties" file accordingly!

* 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}.
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add code sample to the Javadoc in a similar way we do it for other main public methods?

@@ -91,21 +93,35 @@ private static MatcherApplicationType getMatcherApplicationType(Invocation invoc
final int expandedArguments = invocation.getArguments().length;
final int matcherCount = matchers.size();

boolean treatVarargsAsArray = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow. I'm very impressed that you were able to inject the new algorithm here. The code is pretty complex around here. I'd love to see it refactored at some point in the future.

It is complex but you have added fantastic test coverage that I was not able to break down :)

@@ -115,4 +115,49 @@ public void shouldDetectWhenOverloadedMethodCalled() throws Exception {
fail();
} catch(WantedButNotInvoked e) {}
}

@Test
public void shouldVerifyVarargsAsArray_nullVarargsArray() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move those tests to a better place? "BasicVerification" is for basic operations. I suggest to move them to existing VarargTest or create a new one like VarargAsArrayTest.

BTW. thank you very much for adding truly comprehensive test coverage!!!


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

verify(mock).mixedVarargs(any(), varargsAsArray(eq((String[]) null)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add coverage for negative cases, e.g. when verification error is thrown for use cases with "varargsAsArray"? This would also help us see the exception message emitted from Mockito, and if the message is handsome.

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

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a new point to the main Mockito Javadoc with a short description of the feature? This is a really cool feature and it deserves a note in main Javadoc. That doc acts as Mockito user guide and also shows notable features. Feel free to bump new minor version.

@fransflippo
Copy link

@paulduffin @mockitoguy Would love to get this into a release version of Mockito! Can I help address some of the issues that @mockitoguy identified perhaps so we can finish it off?

@ChristianSchwarz
Copy link
Contributor

@mockitoguy
@TimvdLippe
Can you create a mockito branch of this PR, so other can work on it and fix the remaining issues?

@TimvdLippe
Copy link
Contributor

@ChristianSchwarz https://github.com/mockito/mockito/tree/capture-vararg-as-array published

@paulduffin
Copy link
Contributor Author

paulduffin commented Feb 26, 2018 via email

@TungDoanDuong
Copy link

TungDoanDuong commented Mar 4, 2019

@mockitoguy
Any update on this issue?

@TimvdLippe
Copy link
Contributor

Closing this PR as stale. Feel free to open a new PR if you are still interested in landing these changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cannot verify varargs parameter as an array ArgumentCaptor can't capture varargs-arrays
7 participants