Skip to content

Commit

Permalink
Merge pull request #26900 from famod/injectspy-spring-repo
Browse files Browse the repository at this point in the history
Add `@InjectSpy.delegate` to evade errors with `default` methods in intermediate spring-data repo interfaces
  • Loading branch information
geoand authored Jul 26, 2022
2 parents 022602f + ac19c12 commit ecce503
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 4 deletions.
5 changes: 5 additions & 0 deletions integration-tests/spring-data-jpa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,15 @@
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectSpy {

/**
* {@code true} will create a mock that <em>delegates</em> all calls to the real bean, instead of creating a regular Mockito
* spy.
* <p/>
* 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.reflect.Field;

import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;

import io.quarkus.arc.runtime.ClientProxyUnwrapper;
Expand All @@ -18,17 +19,18 @@ 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);
}
}
current = current.getSuperclass();
}
}

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

0 comments on commit ecce503

Please sign in to comment.