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);