Skip to content

Commit 3a1a19e

Browse files
authored
Add support for generic types in MockedConstruction and MockedStatic (#3729)
MockedStatic and MockedConstruction threw a MockitoException when the provided type was itself parameterized, suggesting to use a raw type instead. For instance, MockedConstruction<MyGenericType<String>> was not allowed. This commit allows to mock generic types without throwing an exception and without forcing users to deal with 'raw use of parameterized class' warnings. Fixes #2401
1 parent f3c957a commit 3a1a19e

2 files changed

Lines changed: 131 additions & 43 deletions

File tree

mockito-core/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,20 @@ static Class<?> inferParameterizedType(Type type, String name, String sort) {
8484
if (type instanceof ParameterizedType) {
8585
ParameterizedType parameterizedType = (ParameterizedType) type;
8686
Type[] arguments = parameterizedType.getActualTypeArguments();
87-
if (arguments.length == 1) {
88-
if (arguments[0] instanceof Class<?>) {
89-
return (Class<?>) arguments[0];
90-
}
87+
if (arguments.length != 1) {
88+
throw new IllegalArgumentException(
89+
"Incorrect number of type arguments for "
90+
+ name
91+
+ " of type "
92+
+ sort
93+
+ ": expected 1 but received "
94+
+ arguments.length);
9195
}
96+
97+
return (Class<?>)
98+
(arguments[0] instanceof Class<?>
99+
? arguments[0]
100+
: ((ParameterizedType) arguments[0]).getRawType());
92101
}
93102
throw new MockitoException(
94103
join(
@@ -99,6 +108,6 @@ static Class<?> inferParameterizedType(Type type, String name, String sort) {
99108
"",
100109
"@Mock " + sort + "<Sample>",
101110
"",
102-
"as the type parameter. If the type is itself parameterized, it should be specified as raw type."));
111+
"as the type parameter."));
103112
}
104113
}

mockito-core/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java

Lines changed: 117 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,26 @@
66

77
import static org.assertj.core.api.Assertions.assertThat;
88
import static org.assertj.core.api.Assertions.assertThatThrownBy;
9+
import static org.mockito.Mockito.mock;
10+
import static org.mockito.Mockito.when;
911

12+
import java.lang.reflect.ParameterizedType;
13+
import java.lang.reflect.Type;
14+
import java.util.Arrays;
15+
import java.util.Collection;
1016
import java.util.List;
1117

1218
import org.junit.Test;
19+
import org.junit.experimental.runners.Enclosed;
20+
import org.junit.runner.RunWith;
21+
import org.junit.runners.Parameterized;
22+
import org.junit.runners.Parameterized.Parameter;
23+
import org.junit.runners.Parameterized.Parameters;
24+
import org.mockito.MockedConstruction;
1325
import org.mockito.MockedStatic;
1426
import org.mockito.exceptions.base.MockitoException;
1527

28+
@RunWith(Enclosed.class)
1629
public class MockAnnotationProcessorTest {
1730

1831
@SuppressWarnings("unused")
@@ -21,49 +34,115 @@ public class MockAnnotationProcessorTest {
2134
@SuppressWarnings("unused")
2235
private MockedStatic<List<?>> generic;
2336

24-
@SuppressWarnings({"raw", "unused"})
37+
@SuppressWarnings({"rawtypes", "unused"})
2538
private MockedStatic raw;
2639

27-
@Test
28-
public void testNonGeneric() throws Exception {
29-
Class<?> type =
30-
MockAnnotationProcessor.inferParameterizedType(
31-
MockAnnotationProcessorTest.class
32-
.getDeclaredField("nonGeneric")
33-
.getGenericType(),
34-
"nonGeneric",
35-
"Sample");
36-
assertThat(type).isEqualTo(Void.class);
40+
@SuppressWarnings("unused")
41+
private MockedConstruction<Void> nonGenericConstruction;
42+
43+
@SuppressWarnings("unused")
44+
private MockedConstruction<List<?>> genericConstruction;
45+
46+
@SuppressWarnings({"rawtypes", "unused"})
47+
private MockedConstruction rawConstruction;
48+
49+
@RunWith(Parameterized.class)
50+
public static class NonGenericTest {
51+
52+
@Parameters
53+
public static Collection<Object[]> data() {
54+
return Arrays.asList(new Object[][] {{"nonGeneric"}, {"nonGenericConstruction"}});
55+
}
56+
57+
@Parameter public String fieldName;
58+
59+
@Test
60+
public void ensure_non_generic_fields_can_be_inferred() throws Exception {
61+
Class<?> type =
62+
MockAnnotationProcessor.inferParameterizedType(
63+
MockAnnotationProcessorTest.class
64+
.getDeclaredField(fieldName)
65+
.getGenericType(),
66+
fieldName,
67+
"Sample");
68+
assertThat(type).isEqualTo(Void.class);
69+
}
70+
}
71+
72+
@RunWith(Parameterized.class)
73+
public static class GenericTest {
74+
75+
@Parameters
76+
public static Collection<Object[]> data() {
77+
return Arrays.asList(new Object[][] {{"generic"}, {"genericConstruction"}});
78+
}
79+
80+
@Parameter public String fieldName;
81+
82+
@Test
83+
public void ensure_generic_fields_can_be_inferred() throws Exception {
84+
Class<?> type =
85+
MockAnnotationProcessor.inferParameterizedType(
86+
MockAnnotationProcessorTest.class
87+
.getDeclaredField(fieldName)
88+
.getGenericType(),
89+
fieldName,
90+
"Sample");
91+
assertThat(type).isEqualTo(List.class);
92+
}
3793
}
3894

39-
@Test
40-
public void testGeneric() {
41-
assertThatThrownBy(
42-
() -> {
43-
MockAnnotationProcessor.inferParameterizedType(
44-
MockAnnotationProcessorTest.class
45-
.getDeclaredField("generic")
46-
.getGenericType(),
47-
"generic",
48-
"Sample");
49-
})
50-
.isInstanceOf(MockitoException.class)
51-
.hasMessageContaining(
52-
"Mockito cannot infer a static mock from a raw type for generic");
95+
@RunWith(Parameterized.class)
96+
public static class RawTest {
97+
98+
@Parameters
99+
public static Collection<Object[]> data() {
100+
return Arrays.asList(new Object[][] {{"raw"}, {"rawConstruction"}});
101+
}
102+
103+
@Parameter public String fieldName;
104+
105+
@Test
106+
public void ensure_raw_fields_cannot_be_inferred() {
107+
assertThatThrownBy(
108+
() ->
109+
MockAnnotationProcessor.inferParameterizedType(
110+
MockAnnotationProcessorTest.class
111+
.getDeclaredField(fieldName)
112+
.getGenericType(),
113+
fieldName,
114+
"Sample"))
115+
.isInstanceOf(MockitoException.class)
116+
.hasMessageContaining(
117+
"Mockito cannot infer a static mock from a raw type for " + fieldName);
118+
}
53119
}
54120

55-
@Test
56-
public void testRaw() {
57-
assertThatThrownBy(
58-
() -> {
59-
MockAnnotationProcessor.inferParameterizedType(
60-
MockAnnotationProcessorTest.class
61-
.getDeclaredField("raw")
62-
.getGenericType(),
63-
"raw",
64-
"Sample");
65-
})
66-
.isInstanceOf(MockitoException.class)
67-
.hasMessageContaining("Mockito cannot infer a static mock from a raw type for raw");
121+
@RunWith(Parameterized.class)
122+
public static class WrongNumberOfArgsTest {
123+
124+
@Parameters
125+
public static Collection<Object[]> data() {
126+
return Arrays.asList(new Object[][] {{"raw"}, {"rawConstruction"}});
127+
}
128+
129+
@Parameter public String fieldName;
130+
131+
@Test
132+
public void ensure_parameterized_types_with_more_than_one_arg_cannot_be_inferred() {
133+
final ParameterizedType parameterizedType = mock();
134+
when(parameterizedType.getActualTypeArguments())
135+
.thenReturn(new Type[] {String.class, String.class});
136+
137+
assertThatThrownBy(
138+
() ->
139+
MockAnnotationProcessor.inferParameterizedType(
140+
parameterizedType, fieldName, "Sample"))
141+
.isInstanceOf(IllegalArgumentException.class)
142+
.hasMessage(
143+
"Incorrect number of type arguments for "
144+
+ fieldName
145+
+ " of type Sample: expected 1 but received 2");
146+
}
68147
}
69148
}

0 commit comments

Comments
 (0)