Handle mismatch of number of type arguments (#3190)

Fixes #3000

Co-authored-by: Jörg von Frantzius <joerg.frantzius@aperto.com>
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 a62ee40..cf93d97 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
@@ -45,11 +45,19 @@
                     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);
+                    if (actualTypeArguments.length == actualTypeArguments2.length) {
+                        // Recurse on type parameters, so we properly test whether e.g. Wildcard
+                        // bounds have a match
+                        result =
+                                recurseOnTypeArguments(
+                                        injectMocksField,
+                                        actualTypeArguments,
+                                        actualTypeArguments2);
+                    } else {
+                        // the two ParameterizedTypes cannot match because they have unequal
+                        // number of type arguments
+                        result = false;
+                    }
                 }
             } else {
                 // mockType is a non-parameterized Class, i.e. a concrete class.
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 1295df4..d87fd4d 100644
--- a/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockTest.java
+++ b/subprojects/junit-jupiter/src/test/java/org/mockitousage/GenericTypeMockTest.java
@@ -10,6 +10,7 @@
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.mockito.MockitoAnnotations.*;
 
+import java.io.Serializable;
 import java.sql.Time;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -360,4 +361,69 @@
             assertSame(innerList, spiedImpl.changes);
         }
     }
+
+    /* cannot define interface in nested test class RegressionArrayIndexOutOfBoundsExcepton ,
+     * because interface cannot be defined in a non-static inner class,
+     * and nested test class must be non-static in order for JUnit 5 to be able to run it */
+    public interface BaseRepository<E, I extends Serializable> {
+        E findById(I id);
+
+        E save(E entity);
+    }
+
+    /**
+     * Verify regression https://github.com/mockito/mockito/issues/3000 is fixed.
+     */
+    @Nested
+    public class RegressionArrayIndexOutOfBoundsException {
+
+        public class BaseService<E, I extends Serializable> {
+            private BaseRepository<E, I> repository;
+        }
+
+        public class OneRepository implements BaseRepository<Map<String, String>, String> {
+            public Map<String, String> findById(String id) {
+                return Map.of();
+            }
+
+            public Map<String, String> save(Map<String, String> entity) {
+                return entity;
+            }
+        }
+
+        public class TwoRepository implements BaseRepository<Map<Integer, String>, String> {
+            public Map<Integer, String> findById(String id) {
+                return Map.of();
+            }
+
+            public Map<Integer, String> save(Map<Integer, String> entity) {
+                return entity;
+            }
+        }
+
+        public class One {}
+        ;
+
+        public class Two implements Serializable {
+            private static final long serialVersionUID = 1L;
+        }
+
+        public class UnderTest extends BaseService<One, Two> {
+            private OneRepository oneRepository;
+            private TwoRepository twoRepository;
+        }
+
+        @Mock OneRepository oneRepository;
+
+        @Mock TwoRepository twoRepository;
+
+        @InjectMocks UnderTest underTest = new UnderTest();
+
+        @Test
+        public void testNoAioobe() {
+            assertNotNull(oneRepository);
+            assertNotNull(twoRepository);
+            assertNotNull(underTest);
+        }
+    }
 }