diff --git a/integration-tests/spring-data-jpa/pom.xml b/integration-tests/spring-data-jpa/pom.xml index 2c7946ebe43e3..7ab4d615e16cf 100644 --- a/integration-tests/spring-data-jpa/pom.xml +++ b/integration-tests/spring-data-jpa/pom.xml @@ -41,6 +41,11 @@ quarkus-junit5-internal test + + io.quarkus + quarkus-junit5-mockito + test + io.rest-assured rest-assured diff --git a/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/PostRepositorySpyTest.java b/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/PostRepositorySpyTest.java new file mode 100644 index 0000000000000..22333b6453445 --- /dev/null +++ b/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/PostRepositorySpyTest.java @@ -0,0 +1,26 @@ +package io.quarkus.it.spring.data.jpa; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectSpy; + +@QuarkusTest +public class PostRepositorySpyTest { + + // Without delegate = true, the call to the spy will fail with: + // "Cannot call abstract real method on java object!" + @InjectSpy(delegate = true) + PostRepository repo; + + @Test + void testDefaultMethodOfIntermediateRepositoryInSpy() { + doReturn(new Post()).when(repo).findMandatoryById(1111L); + assertNotNull(repo.findMandatoryById(1111L)); + verify(repo).findMandatoryById(1111L); + } +} diff --git a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/InjectSpy.java b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/InjectSpy.java index d21d9f3e3bcb0..0bad87a8a2666 100644 --- a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/InjectSpy.java +++ b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/InjectSpy.java @@ -12,4 +12,15 @@ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface InjectSpy { + + /** + * {@code true} will create a mock that delegates all calls to the real bean, instead of creating a regular Mockito + * spy. + *

+ * You should try this mode when you get errors like "Cannot call abstract real method on java object!" when calling a + * {@code default} interface method of a spied bean. + * + * @see org.mockito.AdditionalAnswers#delegatesTo(Object) + */ + boolean delegate() default false; } diff --git a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java index b292e0d18700a..c4a62cc5143cd 100644 --- a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java +++ b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java @@ -2,6 +2,7 @@ import java.lang.reflect.Field; +import org.mockito.AdditionalAnswers; import org.mockito.Mockito; import io.quarkus.arc.runtime.ClientProxyUnwrapper; @@ -18,7 +19,7 @@ public void afterConstruct(Object testInstance) { InjectSpy injectSpyAnnotation = field.getAnnotation(InjectSpy.class); if (injectSpyAnnotation != null) { Object beanInstance = CreateMockitoMocksCallback.getBeanInstance(testInstance, field, InjectSpy.class); - Object spy = createSpyAndSetTestField(testInstance, field, beanInstance); + Object spy = createSpyAndSetTestField(testInstance, field, beanInstance, injectSpyAnnotation.delegate()); MockitoMocksTracker.track(testInstance, spy, beanInstance); } } @@ -26,9 +27,10 @@ public void afterConstruct(Object testInstance) { } } - private Object createSpyAndSetTestField(Object testInstance, Field field, Object beanInstance) { - ClientProxyUnwrapper unwrapper = new ClientProxyUnwrapper(); - Object spy = Mockito.spy(unwrapper.apply(beanInstance)); + private Object createSpyAndSetTestField(Object testInstance, Field field, Object beanInstance, boolean delegate) { + Object unwrapped = new ClientProxyUnwrapper().apply(beanInstance); + Object spy = delegate ? Mockito.mock(unwrapped.getClass(), AdditionalAnswers.delegatesTo(unwrapped)) + : Mockito.spy(unwrapped); field.setAccessible(true); try { field.set(testInstance, spy);