Skip to content

Commit

Permalink
Merge pull request #2645 from mockito/interface-annotations
Browse files Browse the repository at this point in the history
Reintroduce inheriting type annotations from interfaces if only one interface is mocked, including additional interfaces.
  • Loading branch information
raphw committed May 20, 2022
2 parents 94e9797 + a3d57fd commit d7a8ae0
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 13 deletions.
Expand Up @@ -222,24 +222,32 @@ public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
}
}
// Graal requires that the byte code of classes is identical what requires that interfaces
// are always
// defined in the exact same order. Therefore, we add an interface to the interface set if
// not mocking
// a class when Graal is active.
// are always defined in the exact same order. Therefore, we add an interface to the
// interface set if not mocking a class when Graal is active.
@SuppressWarnings("unchecked")
Class<T> target =
GraalImageCode.getCurrent().isDefined() && features.mockedType.isInterface()
? (Class<T>) Object.class
: features.mockedType;
// If we create a mock for an interface with additional interfaces implemented, we do not
// want to preserve the annotations of either interface. The caching mechanism does not
// consider the order of these interfaces and the same mock class might be reused for
// either order. Also, it does not have clean semantics as annotations are not normally
// preserved for interfaces in Java.
Annotation[] annotationsOnType;
if (features.stripAnnotations) {
annotationsOnType = new Annotation[0];
} else if (!features.mockedType.isInterface() || features.interfaces.isEmpty()) {
annotationsOnType = features.mockedType.getAnnotations();
} else {
annotationsOnType = new Annotation[0];
}
DynamicType.Builder<T> builder =
byteBuddy
.subclass(target)
.name(name)
.ignoreAlso(BytecodeGenerator.isGroovyMethod(false))
.annotateType(
features.stripAnnotations || features.mockedType.isInterface()
? new Annotation[0]
: features.mockedType.getAnnotations())
.annotateType(annotationsOnType)
.implement(
new ArrayList<>(
GraalImageCode.getCurrent().isDefined()
Expand Down
Expand Up @@ -12,6 +12,7 @@
import org.mockito.internal.creation.MockSettingsImpl;
import org.mockito.plugins.MockMaker;

import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Observable;
Expand Down Expand Up @@ -79,9 +80,8 @@ public void is_type_mockable_give_empty_reason_if_type_is_mockable() {
}

@Test
public void mock_type_with_annotations() throws Exception {
MockSettingsImpl<ClassWithAnnotation> mockSettings =
new MockSettingsImpl<ClassWithAnnotation>();
public void mock_class_with_annotations() throws Exception {
MockSettingsImpl<ClassWithAnnotation> mockSettings = new MockSettingsImpl<>();
mockSettings.setTypeToMock(ClassWithAnnotation.class);

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

@Test
public void mock_class_with_annotations_with_additional_interface() throws Exception {
MockSettingsImpl<ClassWithAnnotation> mockSettings = new MockSettingsImpl<>();
mockSettings.setTypeToMock(ClassWithAnnotation.class);
mockSettings.extraInterfaces(Serializable.class);

ClassWithAnnotation proxy = mockMaker.createMock(mockSettings, dummyHandler());

assertThat(proxy.getClass().isAnnotationPresent(SampleAnnotation.class)).isTrue();
assertThat(proxy.getClass().getAnnotation(SampleAnnotation.class).value()).isEqualTo("foo");

assertThat(
proxy.getClass()
.getMethod("sampleMethod")
.isAnnotationPresent(SampleAnnotation.class))
.isTrue();
assertThat(
proxy.getClass()
.getMethod("sampleMethod")
.getAnnotation(SampleAnnotation.class)
.value())
.isEqualTo("bar");
}

@Test
public void mock_interface_with_annotations() throws Exception {
MockSettingsImpl<InterfaceWithAnnotation> mockSettings = new MockSettingsImpl<>();
mockSettings.setTypeToMock(InterfaceWithAnnotation.class);

InterfaceWithAnnotation proxy = mockMaker.createMock(mockSettings, dummyHandler());

assertThat(proxy.getClass().isAnnotationPresent(SampleAnnotation.class)).isTrue();
assertThat(proxy.getClass().getAnnotation(SampleAnnotation.class).value()).isEqualTo("foo");

assertThat(
proxy.getClass()
.getMethod("sampleMethod")
.isAnnotationPresent(SampleAnnotation.class))
.isTrue();
assertThat(
proxy.getClass()
.getMethod("sampleMethod")
.getAnnotation(SampleAnnotation.class)
.value())
.isEqualTo("bar");
}

@Test
public void mock_interface_with_annotations_with_additional_interface() throws Exception {
MockSettingsImpl<InterfaceWithAnnotation> mockSettings = new MockSettingsImpl<>();
mockSettings.setTypeToMock(InterfaceWithAnnotation.class);
mockSettings.extraInterfaces(Serializable.class);

InterfaceWithAnnotation proxy = mockMaker.createMock(mockSettings, dummyHandler());

assertThat(proxy.getClass().isAnnotationPresent(SampleAnnotation.class)).isFalse();

assertThat(
proxy.getClass()
.getMethod("sampleMethod")
.isAnnotationPresent(SampleAnnotation.class))
.isTrue();
assertThat(
proxy.getClass()
.getMethod("sampleMethod")
.getAnnotation(SampleAnnotation.class)
.value())
.isEqualTo("bar");
}

@Test
public void mock_type_without_annotations() throws Exception {
MockSettingsImpl<ClassWithAnnotation> mockSettings =
new MockSettingsImpl<ClassWithAnnotation>();
MockSettingsImpl<ClassWithAnnotation> mockSettings = new MockSettingsImpl<>();
mockSettings.setTypeToMock(ClassWithAnnotation.class);
mockSettings.withoutAnnotations();

Expand Down Expand Up @@ -138,4 +207,11 @@ public void sampleMethod() {
throw new UnsupportedOperationException();
}
}

@SampleAnnotation("foo")
public interface InterfaceWithAnnotation {

@SampleAnnotation("bar")
void sampleMethod();
}
}

0 comments on commit d7a8ae0

Please sign in to comment.