Repository: mockito/mockito
Issue: ClassCastException when combining InjectMocks with generified Mock field

When calling `MockitoAnnotations.openMocks(this);` on a class that contains an `@InjectMocks` target, a ClassCastException occurs when the target has a field that is generified and provided by one of the mocks in said class.

```java
public class OpenMocksTest
{
	@InjectMocks
	protected ConcreteJob job;

	@Mock
	JobInstance<ConcreteJob> instance;

	@BeforeEach
	public void initMocks()
	{
		openMocks(this);
	}

	@Test
	public void testMockExists()
	{
		assertNotNull(job);
	}
}
```

I've attached a Maven project which demonstrates this flaw. Downgrading the Mockito version to `5.1.1` fixes the issue.

[mockito-5.2.0-bug.zip](https://github.com/mockito/mockito/files/11123059/mockito-5.2.0-bug.zip)


Issue comments:
Probably a regression introduced in #2923. @jfrantzius can you please take a look?
Looking at it...
That's an interesting somehow recursive construct  that I'm surprised the compiler accepts, but then there surely shouldn't be a `ClassCastException` anyway ...
```java
public class ConcreteJob extends AbstractJob<ConcreteJob>
```
(a class that uses itself as a type parameter for its superclass? 🤯 )
@TimvdLippe WDYT?

Pull request review comments:

Patch that resolved the issue:
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 343605f370..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
@@ -15,6 +15,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Stream;
 
 import org.mockito.internal.util.MockUtil;
 
@@ -28,26 +29,38 @@ public TypeBasedCandidateFilter(MockCandidateFilter next) {
 
     protected boolean isCompatibleTypes(Type typeToMock, Type mockType, Field injectMocksField) {
         boolean result = false;
-        if (typeToMock instanceof ParameterizedType && mockType instanceof ParameterizedType) {
-            // ParameterizedType.equals() is documented as:
-            // "Instances of classes that implement this interface must implement
-            // an equals() method that equates any two instances that share the
-            // same generic type declaration and have equal type parameters."
-            // Unfortunately, e.g. Wildcard parameter "?" doesn't equal java.lang.String,
-            // and e.g. Set doesn't equal TreeSet, so roll our own comparison if
-            // ParameterizedTypeImpl.equals() returns false
-            if (typeToMock.equals(mockType)) {
-                result = true;
+        if (typeToMock instanceof ParameterizedType) {
+            if (mockType instanceof ParameterizedType) {
+                // ParameterizedType.equals() is documented as:
+                // "Instances of classes that implement this interface must implement
+                // an equals() method that equates any two instances that share the
+                // same generic type declaration and have equal type parameters."
+                // Unfortunately, e.g. Wildcard parameter "?" doesn't equal java.lang.String,
+                // and e.g. Set doesn't equal TreeSet, so roll our own comparison if
+                // ParameterizedTypeImpl.equals() returns false
+                if (typeToMock.equals(mockType)) {
+                    result = true;
+                } else {
+                    ParameterizedType genericTypeToMock = (ParameterizedType) typeToMock;
+                    ParameterizedType genericMockType = (ParameterizedType) mockType;
+                    Type[] actualTypeArguments = genericTypeToMock.getActualTypeArguments();
+                    Type[] actualTypeArguments2 = genericMockType.getActualTypeArguments();
+                    // Recurse on type parameters, so we properly test whether e.g. Wildcard bounds
+                    // have a match
+                    result =
+                            recurseOnTypeArguments(
+                                    injectMocksField, actualTypeArguments, actualTypeArguments2);
+                }
             } else {
-                ParameterizedType genericTypeToMock = (ParameterizedType) typeToMock;
-                ParameterizedType genericMockType = (ParameterizedType) mockType;
-                Type[] actualTypeArguments = genericTypeToMock.getActualTypeArguments();
-                Type[] actualTypeArguments2 = genericMockType.getActualTypeArguments();
-                // Recurse on type parameters, so we properly test whether e.g. Wildcard bounds
-                // have a match
+                // mockType is a non-parameterized Class, i.e. a concrete class.
+                // so walk concrete class' type hierarchy
+                Class<?> concreteMockClass = (Class<?>) mockType;
+                Stream<Type> mockSuperTypes = getSuperTypes(concreteMockClass);
                 result =
-                        recurseOnTypeArguments(
-                                injectMocksField, actualTypeArguments, actualTypeArguments2);
+                        mockSuperTypes.anyMatch(
+                                mockSuperType ->
+                                        isCompatibleTypes(
+                                                typeToMock, mockSuperType, injectMocksField));
             }
         } else if (typeToMock instanceof WildcardType) {
             WildcardType wildcardTypeToMock = (WildcardType) typeToMock;
@@ -56,12 +69,19 @@ protected boolean isCompatibleTypes(Type typeToMock, Type mockType, Field inject
                     Arrays.stream(upperBounds)
                             .anyMatch(t -> isCompatibleTypes(t, mockType, injectMocksField));
         } else if (typeToMock instanceof Class && mockType instanceof Class) {
-            result = ((Class) typeToMock).isAssignableFrom((Class) mockType);
+            result = ((Class<?>) typeToMock).isAssignableFrom((Class<?>) mockType);
         } // no need to check for GenericArrayType, as Mockito cannot mock this anyway
 
         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;
@@ -76,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(
@@ -119,12 +153,12 @@ public OngoingInjector filterCandidate(
         List<Object> mockTypeMatches = new ArrayList<>();
         for (Object mock : mocks) {
             if (candidateFieldToBeInjected.getType().isAssignableFrom(mock.getClass())) {
-                Type genericMockType = MockUtil.getMockSettings(mock).getGenericTypeToMock();
-                Type genericType = candidateFieldToBeInjected.getGenericType();
-                boolean bothHaveGenericTypeInfo = genericType != null && genericMockType != null;
-                if (bothHaveGenericTypeInfo) {
+                Type mockType = MockUtil.getMockSettings(mock).getGenericTypeToMock();
+                Type typeToMock = candidateFieldToBeInjected.getGenericType();
+                boolean bothHaveTypeInfo = typeToMock != null && mockType != null;
+                if (bothHaveTypeInfo) {
                     // be more specific if generic type information is available
-                    if (isCompatibleTypes(genericType, genericMockType, injectMocksField)) {
+                    if (isCompatibleTypes(typeToMock, mockType, injectMocksField)) {
                         mockTypeMatches.add(mock);
                     } // else filter out mock, as generic types don't match
                 } else {


Test patch:
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 a837f0149b..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,8 +8,10 @@
 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;
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashSet;
@@ -18,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;
@@ -260,4 +263,87 @@ void testWithTypeParameters() {
         }
     }
 
+    @Nested
+    public class InjectConcreteClassInFieldWithTypeParameter {
+        public class UnderTestWithTypeParameter<T> {
+            List<T> tList;
+        }
+
+        public class ConcreteStringList extends ArrayList<String> {}
+
+        @Mock
+        ConcreteStringList concreteStringList;
+
+        @InjectMocks
+        UnderTestWithTypeParameter<String> underTestWithTypeParameters = new UnderTestWithTypeParameter<String>();
+
+        @Test
+        void testWithTypeParameters() {
+            assertNotNull(concreteStringList);
+
+            // verify that we can match the type parameters of the class under test
+            assertEquals(concreteStringList, underTestWithTypeParameters.tList);
+        }
+    }
+
+    @Nested
+    public class NoneMatchInjectConcreteClassInFieldWithTypeParameterTest {
+        public class UnderTestWithTypeParameter<T> {
+            List<T> tList;
+        }
+
+        public class ConcreteStringList extends ArrayList<String> {}
+
+        @Mock
+        ConcreteStringList concreteStringList;
+
+        @InjectMocks
+        UnderTestWithTypeParameter<Integer> underTestWithTypeParameters = new UnderTestWithTypeParameter<Integer>();
+
+        @Test
+        void testWithTypeParameters() {
+            assertNotNull(concreteStringList);
+
+            // verify that when no concrete type candidate matches, none is injected
+            assertNull(underTestWithTypeParameters.tList);
+        }
+    }
+
+    /**
+     * 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);
+        }
+
+
+    }
+
 }
+