diff --git a/src/main/java/org/mockito/internal/configuration/injection/filter/TypeBasedCandidateFilter.java b/src/main/java/org/mockito/internal/configuration/injection/filter/TypeBasedCandidateFilter.java index e65557f606..a66ab52144 100644 --- a/src/main/java/org/mockito/internal/configuration/injection/filter/TypeBasedCandidateFilter.java +++ b/src/main/java/org/mockito/internal/configuration/injection/filter/TypeBasedCandidateFilter.java @@ -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 mockInterfaces = Arrays.stream(concreteMockClass.getGenericInterfaces()); - Stream mockSuperTypes = Stream.concat(mockInterfaces, Stream.of(concreteMockClass.getGenericSuperclass())); - result = mockSuperTypes.anyMatch(mockSuperType -> isCompatibleTypes(typeToMock, mockSuperType, injectMocksField)); + Class concreteMockClass = (Class) mockType; + Stream mockSuperTypes = getSuperTypes(concreteMockClass); + result = + mockSuperTypes.anyMatch( + mockSuperType -> + isCompatibleTypes( + typeToMock, mockSuperType, injectMocksField)); } } else if (typeToMock instanceof WildcardType) { WildcardType wildcardTypeToMock = (WildcardType) typeToMock; @@ -75,6 +75,13 @@ protected boolean isCompatibleTypes(Type typeToMock, Type mockType, Field inject return result; } + private Stream getSuperTypes(Class concreteMockClass) { + Stream mockInterfaces = Arrays.stream(concreteMockClass.getGenericInterfaces()); + Stream mockSuperTypes = + Stream.concat(mockInterfaces, Stream.of(concreteMockClass.getGenericSuperclass())); + return mockSuperTypes; + } + private boolean recurseOnTypeArguments( Field injectMocksField, Type[] actualTypeArguments, Type[] actualTypeArguments2) { boolean isCompatible = true; @@ -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 underTest = .. - Type[] injectMocksFieldTypeParameters = - ((ParameterizedType) injectMocksField.getGenericType()) - .getActualTypeArguments(); - // Find index of given TypeVariable where it was defined, e.g. 0 for T1 in - // ClassUnderTest - // (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 + // (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 would be the String in + // ClassUnderTest 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 would be the String in - // ClassUnderTest underTest = .. - isCompatible &= - isCompatibleTypes( - injectMocksFieldTypeParameters[variableIndex], - actualTypeArgument2, - injectMocksField); } else { isCompatible &= isCompatibleTypes( diff --git a/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockTest.java b/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockTest.java index fed09a49af..4decb2e2e2 100644 --- a/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockTest.java +++ b/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockTest.java @@ -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; @@ -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; @@ -307,4 +309,41 @@ void testWithTypeParameters() { } } + /** + * Verify regression https://github.com/mockito/mockito/issues/2958 is fixed. + */ + @Nested + public class RegressionClassCastException { + public class AbstractUnderTest> { + UnderTestInstance instance; + } + + public class UnderTestInstance> { + } + + public class ConcreteUnderTest extends AbstractUnderTest { + } + + @Mock + UnderTestInstance instanceMock; + + @InjectMocks + protected ConcreteUnderTest concreteUnderTest = new ConcreteUnderTest(); + + @BeforeEach + public void initMocks() + { + openMocks(this); + } + + @Test + public void testMockExists() { + assertNotNull(instanceMock); + assertEquals(instanceMock, concreteUnderTest.instance); + } + + + } + } +