|
16 | 16 |
|
17 | 17 | package org.springframework.boot.test.mock.mockito;
|
18 | 18 |
|
| 19 | +import java.lang.annotation.Annotation; |
19 | 20 | import java.lang.reflect.Field;
|
| 21 | +import java.util.LinkedHashSet; |
| 22 | +import java.util.Set; |
20 | 23 | import java.util.function.BiConsumer;
|
21 | 24 |
|
| 25 | +import org.mockito.Captor; |
| 26 | +import org.mockito.MockitoAnnotations; |
| 27 | + |
22 | 28 | import org.springframework.test.context.TestContext;
|
23 | 29 | 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; |
24 | 32 | import org.springframework.test.context.support.AbstractTestExecutionListener;
|
25 | 33 | import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
26 | 34 | import org.springframework.util.ReflectionUtils;
|
| 35 | +import org.springframework.util.ReflectionUtils.FieldCallback; |
27 | 36 |
|
28 | 37 | /**
|
29 | 38 | * {@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. |
31 | 42 | * <p>
|
32 | 43 | * To use the automatic reset support of {@code @MockBean} and {@code @SpyBean}, configure
|
33 | 44 | * {@link ResetMocksTestExecutionListener} as well.
|
|
37 | 48 | * @author Moritz Halbritter
|
38 | 49 | * @since 1.4.2
|
39 | 50 | * @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}. |
42 | 53 | */
|
43 | 54 | @SuppressWarnings("removal")
|
44 | 55 | @Deprecated(since = "3.4.0", forRemoval = true)
|
45 | 56 | public class MockitoTestExecutionListener extends AbstractTestExecutionListener {
|
46 | 57 |
|
| 58 | + private static final String MOCKS_ATTRIBUTE_NAME = MockitoTestExecutionListener.class.getName() + ".mocks"; |
| 59 | + |
47 | 60 | @Override
|
48 | 61 | public final int getOrder() {
|
49 | 62 | return 1950;
|
50 | 63 | }
|
51 | 64 |
|
52 | 65 | @Override
|
53 | 66 | public void prepareTestInstance(TestContext testContext) throws Exception {
|
| 67 | + closeMocks(testContext); |
| 68 | + initMocks(testContext); |
54 | 69 | injectFields(testContext);
|
55 | 70 | }
|
56 | 71 |
|
57 | 72 | @Override
|
58 | 73 | public void beforeTestMethod(TestContext testContext) throws Exception {
|
59 | 74 | if (Boolean.TRUE.equals(
|
60 | 75 | testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) {
|
| 76 | + closeMocks(testContext); |
| 77 | + initMocks(testContext); |
61 | 78 | reinjectFields(testContext);
|
62 | 79 | }
|
63 | 80 | }
|
64 | 81 |
|
| 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 | + |
65 | 111 | private void injectFields(TestContext testContext) {
|
66 | 112 | postProcessFields(testContext, (mockitoField, postProcessor) -> postProcessor.inject(mockitoField.field,
|
67 | 113 | mockitoField.target, mockitoField.definition));
|
@@ -90,6 +136,28 @@ private void postProcessFields(TestContext testContext, BiConsumer<MockitoField,
|
90 | 136 | }
|
91 | 137 | }
|
92 | 138 |
|
| 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 | + |
93 | 161 | private static final class MockitoField {
|
94 | 162 |
|
95 | 163 | private final Field field;
|
|
0 commit comments