Skip to content

Commit 1b6b9ef

Browse files
committed
Reinstate init of Mockito mocks in test execution listener
Closes gh-42708
1 parent 2014176 commit 1b6b9ef

File tree

4 files changed

+79
-6
lines changed

4 files changed

+79
-6
lines changed

spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,29 @@
1616

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

19+
import java.lang.annotation.Annotation;
1920
import java.lang.reflect.Field;
21+
import java.util.LinkedHashSet;
22+
import java.util.Set;
2023
import java.util.function.BiConsumer;
2124

25+
import org.mockito.Captor;
26+
import org.mockito.MockitoAnnotations;
27+
2228
import org.springframework.test.context.TestContext;
2329
import org.springframework.test.context.TestExecutionListener;
30+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
31+
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
2432
import org.springframework.test.context.support.AbstractTestExecutionListener;
2533
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
2634
import org.springframework.util.ReflectionUtils;
35+
import org.springframework.util.ReflectionUtils.FieldCallback;
2736

2837
/**
2938
* {@link TestExecutionListener} to enable {@link MockBean @MockBean} and
30-
* {@link SpyBean @SpyBean} support.
39+
* {@link SpyBean @SpyBean} support. Also triggers
40+
* {@link MockitoAnnotations#openMocks(Object)} when any Mockito annotations used,
41+
* primarily to allow {@link Captor @Captor} annotations.
3142
* <p>
3243
* To use the automatic reset support of {@code @MockBean} and {@code @SpyBean}, configure
3344
* {@link ResetMocksTestExecutionListener} as well.
@@ -37,31 +48,66 @@
3748
* @author Moritz Halbritter
3849
* @since 1.4.2
3950
* @see ResetMocksTestExecutionListener
40-
* @deprecated since 3.4.0 for removal in 3.6.0 in favor of
41-
* {@link org.springframework.test.context.bean.override.mockito.MockitoTestExecutionListener}
51+
* @deprecated since 3.4.0 for removal in 3.6.0 in favor of Spring Framework's support for
52+
* {@link MockitoBean} and {@link MockitoSpyBean}.
4253
*/
4354
@SuppressWarnings("removal")
4455
@Deprecated(since = "3.4.0", forRemoval = true)
4556
public class MockitoTestExecutionListener extends AbstractTestExecutionListener {
4657

58+
private static final String MOCKS_ATTRIBUTE_NAME = MockitoTestExecutionListener.class.getName() + ".mocks";
59+
4760
@Override
4861
public final int getOrder() {
4962
return 1950;
5063
}
5164

5265
@Override
5366
public void prepareTestInstance(TestContext testContext) throws Exception {
67+
closeMocks(testContext);
68+
initMocks(testContext);
5469
injectFields(testContext);
5570
}
5671

5772
@Override
5873
public void beforeTestMethod(TestContext testContext) throws Exception {
5974
if (Boolean.TRUE.equals(
6075
testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) {
76+
closeMocks(testContext);
77+
initMocks(testContext);
6178
reinjectFields(testContext);
6279
}
6380
}
6481

82+
@Override
83+
public void afterTestMethod(TestContext testContext) throws Exception {
84+
closeMocks(testContext);
85+
}
86+
87+
@Override
88+
public void afterTestClass(TestContext testContext) throws Exception {
89+
closeMocks(testContext);
90+
}
91+
92+
private void initMocks(TestContext testContext) {
93+
if (hasMockitoAnnotations(testContext)) {
94+
testContext.setAttribute(MOCKS_ATTRIBUTE_NAME, MockitoAnnotations.openMocks(testContext.getTestInstance()));
95+
}
96+
}
97+
98+
private void closeMocks(TestContext testContext) throws Exception {
99+
Object mocks = testContext.getAttribute(MOCKS_ATTRIBUTE_NAME);
100+
if (mocks instanceof AutoCloseable closeable) {
101+
closeable.close();
102+
}
103+
}
104+
105+
private boolean hasMockitoAnnotations(TestContext testContext) {
106+
MockitoAnnotationCollection collector = new MockitoAnnotationCollection();
107+
ReflectionUtils.doWithFields(testContext.getTestClass(), collector);
108+
return collector.hasAnnotations();
109+
}
110+
65111
private void injectFields(TestContext testContext) {
66112
postProcessFields(testContext, (mockitoField, postProcessor) -> postProcessor.inject(mockitoField.field,
67113
mockitoField.target, mockitoField.definition));
@@ -90,6 +136,28 @@ private void postProcessFields(TestContext testContext, BiConsumer<MockitoField,
90136
}
91137
}
92138

139+
/**
140+
* {@link FieldCallback} to collect Mockito annotations.
141+
*/
142+
private static final class MockitoAnnotationCollection implements FieldCallback {
143+
144+
private final Set<Annotation> annotations = new LinkedHashSet<>();
145+
146+
@Override
147+
public void doWith(Field field) throws IllegalArgumentException {
148+
for (Annotation annotation : field.getDeclaredAnnotations()) {
149+
if (annotation.annotationType().getName().startsWith("org.mockito")) {
150+
this.annotations.add(annotation);
151+
}
152+
}
153+
}
154+
155+
boolean hasAnnotations() {
156+
return !this.annotations.isEmpty();
157+
}
158+
159+
}
160+
93161
private static final class MockitoField {
94162

95163
private final Field field;

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/nestedtests/InheritedNestedTestConfigurationTests.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.boot.test.context.nestedtests;
1818

19-
import org.junit.jupiter.api.Disabled;
2019
import org.junit.jupiter.api.Nested;
2120
import org.junit.jupiter.api.Test;
2221

@@ -40,7 +39,6 @@
4039
*/
4140
@SpringBootTest(classes = AppConfiguration.class)
4241
@Import(ActionPerformer.class)
43-
@Disabled("https://github.com/spring-projects/spring-framework/issues/33676")
4442
class InheritedNestedTestConfigurationTests {
4543

4644
@MockitoBean

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerIntegrationTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
@SuppressWarnings("removal")
5555
@Deprecated(since = "3.4.0", forRemoval = true)
5656
@ExtendWith(SpringExtension.class)
57-
@Disabled("https://github.com/spring-projects/spring-framework/issues/33690")
5857
class MockitoTestExecutionListenerIntegrationTests {
5958

6059
@Nested

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ class MockitoTestExecutionListenerTests {
5656
@Mock
5757
private MockitoPostProcessor postProcessor;
5858

59+
@Test
60+
void prepareTestInstanceShouldInitMockitoAnnotations() throws Exception {
61+
WithMockitoAnnotations instance = new WithMockitoAnnotations();
62+
this.listener.prepareTestInstance(mockTestContext(instance));
63+
assertThat(instance.mock).isNotNull();
64+
assertThat(instance.captor).isNotNull();
65+
}
66+
5967
@Test
6068
void prepareTestInstanceShouldInjectMockBean() throws Exception {
6169
given(this.applicationContext.getBean(MockitoPostProcessor.class)).willReturn(this.postProcessor);

0 commit comments

Comments
 (0)