Skip to content

Commit

Permalink
fixes mockito#2958
Browse files Browse the repository at this point in the history
  • Loading branch information
Jörg von Frantzius committed Apr 3, 2023
1 parent f3599d2 commit f6eecf1
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 29 deletions.
Expand Up @@ -53,14 +53,14 @@ protected boolean isCompatibleTypes(Type typeToMock, Type mockType, Field inject
}
} else {
// mockType is a non-parameterized Class, i.e. a concrete class.
// We came here even though isAssignableFrom(), but that doesn't
// take type parameters into account due to Java type erasure,
// so walk concrete class' type hierarchy
// TODO was wäre es anders herum, typeToMock ist concrete, und mockType ist ParameterizedType?
Class<?> concreteMockClass = (Class<?>)mockType;
Stream<Type> mockInterfaces = Arrays.stream(concreteMockClass.getGenericInterfaces());
Stream<Type> mockSuperTypes = Stream.concat(mockInterfaces, Stream.of(concreteMockClass.getGenericSuperclass()));
result = mockSuperTypes.anyMatch(mockSuperType -> isCompatibleTypes(typeToMock, mockSuperType, injectMocksField));
Class<?> concreteMockClass = (Class<?>) mockType;
Stream<Type> mockSuperTypes = getSuperTypes(concreteMockClass);
result =
mockSuperTypes.anyMatch(
mockSuperType ->
isCompatibleTypes(
typeToMock, mockSuperType, injectMocksField));
}
} else if (typeToMock instanceof WildcardType) {
WildcardType wildcardTypeToMock = (WildcardType) typeToMock;
Expand All @@ -75,6 +75,13 @@ protected boolean isCompatibleTypes(Type typeToMock, Type mockType, Field inject
return result;
}

private Stream<Type> getSuperTypes(Class<?> concreteMockClass) {
Stream<Type> mockInterfaces = Arrays.stream(concreteMockClass.getGenericInterfaces());
Stream<Type> mockSuperTypes =
Stream.concat(mockInterfaces, Stream.of(concreteMockClass.getGenericSuperclass()));
return mockSuperTypes;
}

private boolean recurseOnTypeArguments(
Field injectMocksField, Type[] actualTypeArguments, Type[] actualTypeArguments2) {
boolean isCompatible = true;
Expand All @@ -89,30 +96,44 @@ private boolean recurseOnTypeArguments(
// The TypeVariable`s actual type is declared by the field containing
// the object under test, i.e. the field annotated with @InjectMocks
// e.g. @InjectMocks ClassUnderTest<String, Integer> underTest = ..
Type[] injectMocksFieldTypeParameters =
((ParameterizedType) injectMocksField.getGenericType())
.getActualTypeArguments();
// Find index of given TypeVariable where it was defined, e.g. 0 for T1 in
// ClassUnderTest<T1, T2>
// (we're always able to find it, otherwise test class wouldn't have compiled))
TypeVariable<?>[] genericTypeParameters =
injectMocksField.getType().getTypeParameters();
int variableIndex = -1;
for (int i2 = 0; i2 < genericTypeParameters.length; i2++) {
if (genericTypeParameters[i2].equals(typeVariable)) {
variableIndex = i2;
break;

Type genericType = injectMocksField.getGenericType();
if (genericType instanceof ParameterizedType) {
Type[] injectMocksFieldTypeParameters =
((ParameterizedType) genericType).getActualTypeArguments();
// Find index of given TypeVariable where it was defined, e.g. 0 for T1 in
// ClassUnderTest<T1, T2>
// (we're always able to find it, otherwise test class wouldn't have compiled))
TypeVariable<?>[] genericTypeParameters =
injectMocksField.getType().getTypeParameters();
int variableIndex = -1;
for (int i2 = 0; i2 < genericTypeParameters.length; i2++) {
if (genericTypeParameters[i2].equals(typeVariable)) {
variableIndex = i2;
break;
}
}
// now test whether actual type for the type variable is compatible, e.g. for
// class ClassUnderTest<T1, T2> {..}
// T1 would be the String in
// ClassUnderTest<String, Integer> underTest = ..
isCompatible &=
isCompatibleTypes(
injectMocksFieldTypeParameters[variableIndex],
actualTypeArgument2,
injectMocksField);
} else {
// must be a concrete class, recurse on super types that may have type
// parameters
isCompatible &=
getSuperTypes((Class<?>) genericType)
.anyMatch(
superType ->
isCompatibleTypes(
superType,
actualTypeArgument2,
injectMocksField));
}
// now test whether actual type for the type variable is compatible, e.g. for
// class ClassUnderTest<T1, T2> {..}
// T1 would be the String in
// ClassUnderTest<String, Integer> underTest = ..
isCompatible &=
isCompatibleTypes(
injectMocksFieldTypeParameters[variableIndex],
actualTypeArgument2,
injectMocksField);
} else {
isCompatible &=
isCompatibleTypes(
Expand Down
Expand Up @@ -8,6 +8,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.MockitoAnnotations.*;

import java.sql.Time;
import java.util.ArrayList;
Expand All @@ -19,6 +20,7 @@
import java.util.Set;
import java.util.TreeSet;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -307,4 +309,41 @@ void testWithTypeParameters() {
}
}

/**
* Verify regression https://github.com/mockito/mockito/issues/2958 is fixed.
*/
@Nested
public class RegressionClassCastException {
public class AbstractUnderTest<A extends AbstractUnderTest<A>> {
UnderTestInstance<A> instance;
}

public class UnderTestInstance<I extends AbstractUnderTest<I>> {
}

public class ConcreteUnderTest extends AbstractUnderTest<ConcreteUnderTest> {
}

@Mock
UnderTestInstance<ConcreteUnderTest> instanceMock;

@InjectMocks
protected ConcreteUnderTest concreteUnderTest = new ConcreteUnderTest();

@BeforeEach
public void initMocks()
{
openMocks(this);
}

@Test
public void testMockExists() {
assertNotNull(instanceMock);
assertEquals(instanceMock, concreteUnderTest.instance);
}


}

}

0 comments on commit f6eecf1

Please sign in to comment.