Skip to content

Commit

Permalink
Adapt to Mockito support in the Test Context Framework
Browse files Browse the repository at this point in the history
This commit updates MockitoTestExecutionListener to not handle mocks
as this is already done in the listener provided by the core framework,
and registration can only happen once.

Integration tests have been left as-is to validate that the presence
of both listeners don't have an unwanted side effect.
  • Loading branch information
snicoll committed May 2, 2024
1 parent caa3f7e commit a6bd461
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,18 @@

package org.springframework.boot.test.mock.mockito;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.BiConsumer;

import org.mockito.Captor;
import org.mockito.MockitoAnnotations;

import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;

/**
* {@link TestExecutionListener} to enable {@link MockBean @MockBean} and
* {@link SpyBean @SpyBean} support. Also triggers
* {@link MockitoAnnotations#openMocks(Object)} when any Mockito annotations used,
* primarily to allow {@link Captor @Captor} annotations.
* {@link SpyBean @SpyBean} support.
* <p>
* To use the automatic reset support of {@code @MockBean} and {@code @SpyBean}, configure
* {@link ResetMocksTestExecutionListener} as well.
Expand All @@ -49,59 +40,24 @@
*/
public class MockitoTestExecutionListener extends AbstractTestExecutionListener {

private static final String MOCKS_ATTRIBUTE_NAME = MockitoTestExecutionListener.class.getName() + ".mocks";

@Override
public final int getOrder() {
return 1950;
}

@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
closeMocks(testContext);
initMocks(testContext);
injectFields(testContext);
}

@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
if (Boolean.TRUE.equals(
testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) {
closeMocks(testContext);
initMocks(testContext);
reinjectFields(testContext);
}
}

@Override
public void afterTestMethod(TestContext testContext) throws Exception {
closeMocks(testContext);
}

@Override
public void afterTestClass(TestContext testContext) throws Exception {
closeMocks(testContext);
}

private void initMocks(TestContext testContext) {
if (hasMockitoAnnotations(testContext)) {
testContext.setAttribute(MOCKS_ATTRIBUTE_NAME, MockitoAnnotations.openMocks(testContext.getTestInstance()));
}
}

private void closeMocks(TestContext testContext) throws Exception {
Object mocks = testContext.getAttribute(MOCKS_ATTRIBUTE_NAME);
if (mocks instanceof AutoCloseable closeable) {
closeable.close();
}
}

private boolean hasMockitoAnnotations(TestContext testContext) {
MockitoAnnotationCollection collector = new MockitoAnnotationCollection();
ReflectionUtils.doWithFields(testContext.getTestClass(), collector);
return collector.hasAnnotations();
}

private void injectFields(TestContext testContext) {
postProcessFields(testContext, (mockitoField, postProcessor) -> postProcessor.inject(mockitoField.field,
mockitoField.target, mockitoField.definition));
Expand Down Expand Up @@ -130,28 +86,6 @@ private void postProcessFields(TestContext testContext, BiConsumer<MockitoField,
}
}

/**
* {@link FieldCallback} to collect Mockito annotations.
*/
private static final class MockitoAnnotationCollection implements FieldCallback {

private final Set<Annotation> annotations = new LinkedHashSet<>();

@Override
public void doWith(Field field) throws IllegalArgumentException {
for (Annotation annotation : field.getDeclaredAnnotations()) {
if (annotation.annotationType().getName().startsWith("org.mockito")) {
this.annotations.add(annotation);
}
}
}

boolean hasAnnotations() {
return !this.annotations.isEmpty();
}

}

private static final class MockitoField {

private final Field field;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -53,14 +53,6 @@ class MockitoTestExecutionListenerTests {
@Mock
private MockitoPostProcessor postProcessor;

@Test
void prepareTestInstanceShouldInitMockitoAnnotations() throws Exception {
WithMockitoAnnotations instance = new WithMockitoAnnotations();
this.listener.prepareTestInstance(mockTestContext(instance));
assertThat(instance.mock).isNotNull();
assertThat(instance.captor).isNotNull();
}

@Test
void prepareTestInstanceShouldInjectMockBean() throws Exception {
given(this.applicationContext.getBean(MockitoPostProcessor.class)).willReturn(this.postProcessor);
Expand Down

0 comments on commit a6bd461

Please sign in to comment.