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

Varargs methods cause ClassCastException in AnswerFunctionalInterfaces #2644

Closed
5 tasks done
perlun opened this issue May 17, 2022 · 4 comments · Fixed by #2664
Closed
5 tasks done

Varargs methods cause ClassCastException in AnswerFunctionalInterfaces #2644

perlun opened this issue May 17, 2022 · 4 comments · Fixed by #2664

Comments

@perlun
Copy link
Contributor

perlun commented May 17, 2022

Hi,

The other day, I discovered that this code does not work:

        doAnswer(answerVoid((VoidAnswer2<String, Object[]>)
                logger::info
        )).when(mock)
                .info(any(), (Object[]) any());

        // This call will throw an exception
        mock.info("Some message with {} {} {}", "three", "parameters", "");

The exception details looks as follows:

class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
	at org.mockito.internal.stubbing.answers.AnswerFunctionalInterfaces$4.answer(AnswerFunctionalInterfaces.java:97)
	at org.mockito.internal.stubbing.answers.AnswerFunctionalInterfaces$4.answer(AnswerFunctionalInterfaces.java:93)
	at org.mockito.internal.stubbing.StubbedInvocationMatcher.answer(StubbedInvocationMatcher.java:42)
	at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:103)
	at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
	at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:34)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:82)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:56)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptAbstract(MockMethodInterceptor.java:161)
	at org.slf4j.Logger$MockitoMock$f4Org7xs.info(Unknown Source)
	at LoggerMockingTest.logger_info_can_be_called_without_exceptions(LoggerMockingTest.java:34)
        [...]

The following works, but feels much less elegant:

        doAnswer( invocation -> {
            String format = invocation.getArgument( 0 );
            Object[] allArguments = invocation.getArguments();
            Object[] arguments = Arrays.copyOfRange( allArguments, 1, allArguments.length );
            logger.info( format, arguments );
            return null;
        } ).when( mock )
                .info( anyString(), (Object[])any() );

Is this to be considered a bug or a feature? 🙂 I get the feeling that varargs (Object[] under the hood) parameters do not really work when used with the answerVoid()-based approach as described above.

Full MCVE

https://github.com/perlun/mockito-slf4-logging-example

Issue checks

  • The mockito message in the stacktrace have useful information, but it didn't help
  • The problematic code (if that's possible) is copied here;
    Note that some configuration are impossible to mock via Mockito
  • Provide versions (mockito / jdk / os / any other relevant information): Reproduced with latest Mockito, 4.5.1
  • Provide a Short, Self Contained, Correct (Compilable), Example of the issue
  • Read the contributing guide
@big-andy-coates
Copy link
Contributor

I've had a bit of a dig and it looks like a bug to me. returnsArgAt has similar issues. I'm happy to have a look into this.

big-andy-coates added a commit to big-andy-coates/mockito that referenced this issue Jun 2, 2022
Fixes: mockito#2644

Fixes issues around vararg handling for the following methods in `AdditionalAnswers`:
 * `returnsFirstArg`
 * `returnsSecondArg`
 * `returnsLastArg`
 * `returnsArgAt`
 * `answer`
 * `answerVoid`

These methods were not correctly handling varargs. For example,

```java
doAnswer(answerVoid(
      (VoidAnswer2<String, Object[]>) logger::info
   )).when(mock)
      .info(any(), (Object[]) any());

mock.info("Some message with {} {} {}", "three", "parameters", "");
```

Would previously have resulted in a `ClassCastException` being thrown from the `mock.info` call.  This was because the `answerVoid` method was not taking into account that the second parameter was a varargs parameter and was attempting to pass the second actual argument `"three"`, rather than the second _raw_ argument `["three", "parameters", ""]`.
big-andy-coates added a commit to big-andy-coates/mockito that referenced this issue Jun 6, 2022
Fixes: mockito#2644

Fixes issues around vararg handling for the following methods in `AdditionalAnswers`:
 * `returnsFirstArg`
 * `returnsSecondArg`
 * `returnsLastArg`
 * `returnsArgAt`
 * `answer`
 * `answerVoid`

These methods were not correctly handling varargs. For example,

```java
doAnswer(answerVoid(
      (VoidAnswer2<String, Object[]>) logger::info
   )).when(mock)
      .info(any(), (Object[]) any());

mock.info("Some message with {} {} {}", "three", "parameters", "");
```

Would previously have resulted in a `ClassCastException` being thrown from the `mock.info` call.  This was because the `answerVoid` method was not taking into account that the second parameter was a varargs parameter and was attempting to pass the second actual argument `"three"`, rather than the second _raw_ argument `["three", "parameters", ""]`.
big-andy-coates added a commit to big-andy-coates/mockito that referenced this issue Jun 6, 2022
Fixes: mockito#2644

Fixes issues around vararg handling for the following methods in `AdditionalAnswers`:
 * `returnsFirstArg`
 * `returnsSecondArg`
 * `returnsLastArg`
 * `returnsArgAt`
 * `answer`
 * `answerVoid`

These methods were not correctly handling varargs. For example,

```java
doAnswer(answerVoid(
      (VoidAnswer2<String, Object[]>) logger::info
   )).when(mock)
      .info(any(), (Object[]) any());

mock.info("Some message with {} {} {}", "three", "parameters", "");
```

Would previously have resulted in a `ClassCastException` being thrown from the `mock.info` call.  This was because the `answerVoid` method was not taking into account that the second parameter was a varargs parameter and was attempting to pass the second actual argument `"three"`, rather than the second _raw_ argument `["three", "parameters", ""]`.
TimvdLippe pushed a commit that referenced this issue Jun 11, 2022
Fixes issues around vararg handling for the following methods in `AdditionalAnswers`:
 * `returnsFirstArg`
 * `returnsSecondArg`
 * `returnsLastArg`
 * `returnsArgAt`
 * `answer`
 * `answerVoid`

These methods were not correctly handling varargs. For example,

```java
doAnswer(answerVoid(
      (VoidAnswer2<String, Object[]>) logger::info
   )).when(mock)
      .info(any(), (Object[]) any());

mock.info("Some message with {} {} {}", "three", "parameters", "");
```

Would previously have resulted in a `ClassCastException` being thrown from the `mock.info` call.  This was because the `answerVoid` method was not taking into account that the second parameter was a varargs parameter and was attempting to pass the second actual argument `"three"`, rather than the second _raw_ argument `["three", "parameters", ""]`.

Fixes #2644
@perlun
Copy link
Contributor Author

perlun commented Jul 4, 2022

Thanks for the fix @big-andy-coates & @TimvdLippe, appreciated!

@perlun
Copy link
Contributor Author

perlun commented Aug 30, 2022

(Verified in our project that the fix indeed fixes the original problem. Thanks! 🙏)

@perlun
Copy link
Contributor Author

perlun commented Nov 18, 2022

Semi-related issue discovered today: #2664 (comment)

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 a pull request may close this issue.

2 participants