I don’t understand why a Class Cast exception is thrown here:
I have a method that takes as parameter a varargs of String
public List<A> getSomething(final String a, final String b, final Date c, final String d, final String e, final String... f) throws MyException {
return valuesFromDB(a, b, c, d, e, f);
}
I have mocked the method implementation
public List<A> getSomethingUnitTest(final String a, final String b, final Date c, final String d, final String e, final String... f) throws MyException {
return valuesFromCSV(a, b, c, d, e, f);
}
on Unit Test using Mockito
@ExtendWith(MockitoExtension.class)
class AlgoTest {
@Test
void testExecute() throws MyException {
when(myRealService.getSomething(anyString(), anyString(), any(Date.class), anyString(), anyString(), any()))
.thenAnswer(i -> myMockedService.getSomething(i.getArgument(0), i.getArgument(1), i.getArgument(2), i.getArgument(3), i.getArgument(4), i.getArgument(5)));
Output output = algo.execute();
assertNotNull(output);
}
}
when I run the test a Class Cast Exception is thrown
java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.String; (java.lang.String and [Ljava.lang.String; are in module java.base of loader 'bootstrap')
at com.cmystuff.alghorithm.AlgoTest.lambda$2(AlgoTest.java:82)
at org.mockito.internal.stubbing.StubbedInvocationMatcher.answer(StubbedInvocationMatcher.java:40)
at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:99)
at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:33)
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.interceptSuperCallable(MockMethodInterceptor.java:141)
any doucumentation:
<String[]> String[] org.mockito.ArgumentMatchers.any()
Matches anything, including nulls and varargs.
getArgument Documentation:
<String[]> String[] org.mockito.invocation.InvocationOnMock.getArgument(int index)
Returns casted argument at the given index. Can lookup in expanded arguments form getArguments().
This method is preferred over getArgument(int, Class) for readability.
Please readthe documentation of getArgument(int, Class) for an overview of situations whenthat method is preferred over this one.
I also tried to use anyString() but I got the same Class Cast Exception
@ExtendWith(MockitoExtension.class)
class AlgoTest {
@Test
void testExecute() throws MyException {
when(myRealService.getSomething(anyString(), anyString(), any(Date.class), anyString(), anyString(), anyString()))
.thenAnswer(i -> myMockedService.getSomething(i.getArgument(0), i.getArgument(1), i.getArgument(2), i.getArgument(3), i.getArgument(4), i.getArgument(5)));
Output output = algo.execute();
assertNotNull(output);
}
}
The issue seems to be related to varargs with one value…
if I pass two values for the varargs parameter
@ExtendWith(MockitoExtension.class)
class AlgoTest {
@Test
void testExecute() throws MyException {
when(myRealService.getSomething(anyString(), anyString(), any(Date.class), anyString(), anyString(), anyString(), anyString()))
.thenAnswer(i -> myMockedService.getSomething(i.getArgument(0), i.getArgument(1), i.getArgument(2), i.getArgument(3), i.getArgument(4), i.getArgument(5), i.getArgument(6)));
Output output = algo.execute();
assertNotNull(output);
}
}
this one will work fine.
>Solution :
Method taking varargs is represented in runtime as a method taking an array as its last argument. The fact that you can pass arguments as separate values in your source code is only syntax sugar.
The problem with your code is that i.getArgument(index) returns each separate value – it does not group varargs into an array.
For example, it you call:
myRealService.getSomething("0", "1", new Date(), "3", "4", "5", "6", "7");
- i.getArgument(5) is equal to "5"
- you pass this value to the varargs method, while you should be passing an array instead
- you can easily copy the values corresponding to original varargs to a new array (of type String[])
when(myRealService.getSomething(
anyString(),
anyString(),
any(Date.class),
anyString(),
anyString(),
any())
).thenAnswer(i -> {
String[] varargFromInvocation = Arrays.copyOfRange(i.getArguments(), 5, i.getArguments().length, String[].class);
return myMockedService.getSomething(
i.getArgument(0),
i.getArgument(1),
i.getArgument(2),
i.getArgument(3),
i.getArgument(4),
varargFromInvocation);
});