Skip to content

Commit d7a8ae0

Browse files
authored
Merge pull request #2645 from mockito/interface-annotations
Reintroduce inheriting type annotations from interfaces if only one interface is mocked, including additional interfaces.
2 parents 94e9797 + a3d57fd commit d7a8ae0

File tree

2 files changed

+97
-13
lines changed

2 files changed

+97
-13
lines changed

src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java

+16-8
Original file line numberDiff line numberDiff line change
@@ -222,24 +222,32 @@ public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
222222
}
223223
}
224224
// Graal requires that the byte code of classes is identical what requires that interfaces
225-
// are always
226-
// defined in the exact same order. Therefore, we add an interface to the interface set if
227-
// not mocking
228-
// a class when Graal is active.
225+
// are always defined in the exact same order. Therefore, we add an interface to the
226+
// interface set if not mocking a class when Graal is active.
229227
@SuppressWarnings("unchecked")
230228
Class<T> target =
231229
GraalImageCode.getCurrent().isDefined() && features.mockedType.isInterface()
232230
? (Class<T>) Object.class
233231
: features.mockedType;
232+
// If we create a mock for an interface with additional interfaces implemented, we do not
233+
// want to preserve the annotations of either interface. The caching mechanism does not
234+
// consider the order of these interfaces and the same mock class might be reused for
235+
// either order. Also, it does not have clean semantics as annotations are not normally
236+
// preserved for interfaces in Java.
237+
Annotation[] annotationsOnType;
238+
if (features.stripAnnotations) {
239+
annotationsOnType = new Annotation[0];
240+
} else if (!features.mockedType.isInterface() || features.interfaces.isEmpty()) {
241+
annotationsOnType = features.mockedType.getAnnotations();
242+
} else {
243+
annotationsOnType = new Annotation[0];
244+
}
234245
DynamicType.Builder<T> builder =
235246
byteBuddy
236247
.subclass(target)
237248
.name(name)
238249
.ignoreAlso(BytecodeGenerator.isGroovyMethod(false))
239-
.annotateType(
240-
features.stripAnnotations || features.mockedType.isInterface()
241-
? new Annotation[0]
242-
: features.mockedType.getAnnotations())
250+
.annotateType(annotationsOnType)
243251
.implement(
244252
new ArrayList<>(
245253
GraalImageCode.getCurrent().isDefined()

src/test/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMakerTest.java

+81-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.mockito.internal.creation.MockSettingsImpl;
1313
import org.mockito.plugins.MockMaker;
1414

15+
import java.io.Serializable;
1516
import java.lang.annotation.Retention;
1617
import java.lang.annotation.RetentionPolicy;
1718
import java.util.Observable;
@@ -79,9 +80,8 @@ public void is_type_mockable_give_empty_reason_if_type_is_mockable() {
7980
}
8081

8182
@Test
82-
public void mock_type_with_annotations() throws Exception {
83-
MockSettingsImpl<ClassWithAnnotation> mockSettings =
84-
new MockSettingsImpl<ClassWithAnnotation>();
83+
public void mock_class_with_annotations() throws Exception {
84+
MockSettingsImpl<ClassWithAnnotation> mockSettings = new MockSettingsImpl<>();
8585
mockSettings.setTypeToMock(ClassWithAnnotation.class);
8686

8787
ClassWithAnnotation proxy = mockMaker.createMock(mockSettings, dummyHandler());
@@ -102,10 +102,79 @@ public void mock_type_with_annotations() throws Exception {
102102
.isEqualTo("bar");
103103
}
104104

105+
@Test
106+
public void mock_class_with_annotations_with_additional_interface() throws Exception {
107+
MockSettingsImpl<ClassWithAnnotation> mockSettings = new MockSettingsImpl<>();
108+
mockSettings.setTypeToMock(ClassWithAnnotation.class);
109+
mockSettings.extraInterfaces(Serializable.class);
110+
111+
ClassWithAnnotation proxy = mockMaker.createMock(mockSettings, dummyHandler());
112+
113+
assertThat(proxy.getClass().isAnnotationPresent(SampleAnnotation.class)).isTrue();
114+
assertThat(proxy.getClass().getAnnotation(SampleAnnotation.class).value()).isEqualTo("foo");
115+
116+
assertThat(
117+
proxy.getClass()
118+
.getMethod("sampleMethod")
119+
.isAnnotationPresent(SampleAnnotation.class))
120+
.isTrue();
121+
assertThat(
122+
proxy.getClass()
123+
.getMethod("sampleMethod")
124+
.getAnnotation(SampleAnnotation.class)
125+
.value())
126+
.isEqualTo("bar");
127+
}
128+
129+
@Test
130+
public void mock_interface_with_annotations() throws Exception {
131+
MockSettingsImpl<InterfaceWithAnnotation> mockSettings = new MockSettingsImpl<>();
132+
mockSettings.setTypeToMock(InterfaceWithAnnotation.class);
133+
134+
InterfaceWithAnnotation proxy = mockMaker.createMock(mockSettings, dummyHandler());
135+
136+
assertThat(proxy.getClass().isAnnotationPresent(SampleAnnotation.class)).isTrue();
137+
assertThat(proxy.getClass().getAnnotation(SampleAnnotation.class).value()).isEqualTo("foo");
138+
139+
assertThat(
140+
proxy.getClass()
141+
.getMethod("sampleMethod")
142+
.isAnnotationPresent(SampleAnnotation.class))
143+
.isTrue();
144+
assertThat(
145+
proxy.getClass()
146+
.getMethod("sampleMethod")
147+
.getAnnotation(SampleAnnotation.class)
148+
.value())
149+
.isEqualTo("bar");
150+
}
151+
152+
@Test
153+
public void mock_interface_with_annotations_with_additional_interface() throws Exception {
154+
MockSettingsImpl<InterfaceWithAnnotation> mockSettings = new MockSettingsImpl<>();
155+
mockSettings.setTypeToMock(InterfaceWithAnnotation.class);
156+
mockSettings.extraInterfaces(Serializable.class);
157+
158+
InterfaceWithAnnotation proxy = mockMaker.createMock(mockSettings, dummyHandler());
159+
160+
assertThat(proxy.getClass().isAnnotationPresent(SampleAnnotation.class)).isFalse();
161+
162+
assertThat(
163+
proxy.getClass()
164+
.getMethod("sampleMethod")
165+
.isAnnotationPresent(SampleAnnotation.class))
166+
.isTrue();
167+
assertThat(
168+
proxy.getClass()
169+
.getMethod("sampleMethod")
170+
.getAnnotation(SampleAnnotation.class)
171+
.value())
172+
.isEqualTo("bar");
173+
}
174+
105175
@Test
106176
public void mock_type_without_annotations() throws Exception {
107-
MockSettingsImpl<ClassWithAnnotation> mockSettings =
108-
new MockSettingsImpl<ClassWithAnnotation>();
177+
MockSettingsImpl<ClassWithAnnotation> mockSettings = new MockSettingsImpl<>();
109178
mockSettings.setTypeToMock(ClassWithAnnotation.class);
110179
mockSettings.withoutAnnotations();
111180

@@ -138,4 +207,11 @@ public void sampleMethod() {
138207
throw new UnsupportedOperationException();
139208
}
140209
}
210+
211+
@SampleAnnotation("foo")
212+
public interface InterfaceWithAnnotation {
213+
214+
@SampleAnnotation("bar")
215+
void sampleMethod();
216+
}
141217
}

0 commit comments

Comments
 (0)