Skip to content

Commit

Permalink
Merge pull request #35606 from manovotn/issue35581
Browse files Browse the repository at this point in the history
Mockito testing - manually activate req. context when creating Mockito spies
  • Loading branch information
geoand authored Aug 30, 2023
2 parents bf886a5 + 1046d33 commit 1c7152b
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.quarkus.it.mockbean;

import static org.junit.jupiter.api.Assertions.assertNotNull;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;

/**
* Tests that Mockito spies can be RequestScoped - this case is different from app scoped/singletons because by the time
* we create spies (right after creating test instance), we need to manually activate req. context for this creation.
*/
@QuarkusTest
public class RequestScopedSpyTest {

@InjectSpy
private RequestBean spiedBean;

@Inject
private SomeOtherBean injectedBean;

@Test
void verifySpyWorks() {
// Executes gracefully
assertNotNull(spiedBean);
injectedBean.pong();
Mockito.verify(spiedBean, Mockito.times(1)).ping();
}

@Nested
class NestedTest {
@Test
void verifyNestedSpyWorks() {
assertNotNull(spiedBean);
injectedBean.pong();
Mockito.verify(spiedBean, Mockito.times(1)).ping();
}
}

@RequestScoped
public static class RequestBean {

public void ping() {

}
}

@ApplicationScoped
public static class SomeOtherBean {

@Inject
RequestBean bean;

public void pong() {
bean.ping();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
package io.quarkus.test.junit.mockito.internal;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

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

import io.quarkus.arc.Arc;
import io.quarkus.arc.ClientProxy;
import io.quarkus.arc.InjectableContext;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.test.junit.callback.QuarkusTestAfterAllCallback;
import io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback;
import io.quarkus.test.junit.callback.QuarkusTestContext;
import io.quarkus.test.junit.mockito.InjectSpy;

public class CreateMockitoSpiesCallback implements QuarkusTestAfterConstructCallback {
public class CreateMockitoSpiesCallback implements QuarkusTestAfterConstructCallback, QuarkusTestAfterAllCallback {

// in nested tests, there are multiple states created before destruction is triggered
private static Set<InjectableContext.ContextState> statesToDestroy = new HashSet<>();

@Override
public void afterConstruct(Object testInstance) {
Class<?> current = testInstance.getClass();
// QuarkusTestAfterConstructCallback can be used in @QuarkusIntegrationTest where there is no Arc
boolean contextPreviouslyActive = Arc.container() != null && Arc.container().requestContext().isActive();
if (!contextPreviouslyActive) {
Arc.container().requestContext().activate();
}
while (current.getSuperclass() != null) {
for (Field field : current.getDeclaredFields()) {
InjectSpy injectSpyAnnotation = field.getAnnotation(InjectSpy.class);
Expand All @@ -28,16 +42,21 @@ public void afterConstruct(Object testInstance) {
}
current = current.getSuperclass();
}
if (!contextPreviouslyActive) {
// only deactivate; we will destroy them in QuarkusTestAfterAllCallback
statesToDestroy.add(Arc.container().requestContext().getState());
Arc.container().requestContext().deactivate();
}
}

private Object createSpyAndSetTestField(Object testInstance, Field field, InstanceHandle<?> beanHandle, boolean delegate) {
Object spy;
// Unwrap the client proxy if needed
Object contextualInstance = ClientProxy.unwrap(beanHandle.get());
if (delegate) {
spy = Mockito.mock(beanHandle.getBean().getImplementationClass(),
AdditionalAnswers.delegatesTo(contextualInstance));
} else {
// Unwrap the client proxy if needed
spy = Mockito.spy(contextualInstance);
}
field.setAccessible(true);
Expand All @@ -49,4 +68,13 @@ private Object createSpyAndSetTestField(Object testInstance, Field field, Instan
return spy;
}

@Override
public void afterAll(QuarkusTestContext context) {
if (!statesToDestroy.isEmpty()) {
for (InjectableContext.ContextState state : statesToDestroy) {
Arc.container().requestContext().destroy(state);
}
statesToDestroy.clear();
}
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
io.quarkus.test.junit.mockito.internal.ResetOuterMockitoMocksCallback
io.quarkus.test.junit.mockito.internal.CreateMockitoSpiesCallback

0 comments on commit 1c7152b

Please sign in to comment.