Skip to content

Commit 0088b9c

Browse files
committed
Honor MockReset strategy for @⁠MockitoBean and @⁠MockitoSpyBean
Commit 6c2cba5 introduced a regression by inadvertently removing the MockReset strategy comparison when resetting @⁠MockitoBean and @⁠MockitoSpyBean mocks. This commit reinstates the MockReset strategy check and introduces tests for this feature. Closes gh-33941
1 parent 2b840ee commit 0088b9c

7 files changed

+220
-6
lines changed

spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockReset.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,10 @@ public static MockSettings apply(MockReset reset, MockSettings settings) {
9999
}
100100

101101
/**
102-
* Get the {@link MockReset} associated with the given mock.
103-
* @param mock the source mock
104-
* @return the reset type (never {@code null})
102+
* Get the {@link MockReset} strategy associated with the given mock.
103+
* @param mock the mock
104+
* @return the reset strategy for the given mock, or {@link MockReset#NONE}
105+
* if no strategy is associated with the given mock
105106
*/
106107
static MockReset get(Object mock) {
107108
MockingDetails mockingDetails = Mockito.mockingDetails(mock);

spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeans.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,18 @@ void add(Object bean) {
3737
this.beans.add(bean);
3838
}
3939

40-
void resetAll() {
41-
this.beans.forEach(Mockito::reset);
40+
/**
41+
* Reset all Mockito beans configured with the supplied {@link MockReset} strategy.
42+
* <p>No mocks will be reset if the supplied strategy is {@link MockReset#NONE}.
43+
*/
44+
void resetAll(MockReset reset) {
45+
if (reset != MockReset.NONE) {
46+
for (Object bean : this.beans) {
47+
if (reset == MockReset.get(bean)) {
48+
Mockito.reset(bean);
49+
}
50+
}
51+
}
4252
}
4353

4454
}

spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListener.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private static void resetMocks(ConfigurableApplicationContext applicationContext
116116
}
117117
}
118118
try {
119-
beanFactory.getBean(MockitoBeans.class).resetAll();
119+
beanFactory.getBean(MockitoBeans.class).resetAll(reset);
120120
}
121121
catch (NoSuchBeanDefinitionException ex) {
122122
// Continue
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.bean.override.mockito;
18+
19+
import org.junit.jupiter.api.AfterAll;
20+
import org.junit.jupiter.api.AfterEach;
21+
import org.junit.jupiter.api.MethodOrderer;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.TestInfo;
24+
import org.junit.jupiter.api.TestMethodOrder;
25+
import org.junit.jupiter.api.extension.AfterEachCallback;
26+
import org.junit.jupiter.api.extension.ExtendWith;
27+
import org.junit.jupiter.api.extension.ExtensionContext;
28+
29+
import org.springframework.test.context.bean.override.mockito.MockResetStrategiesIntegrationTests.MockVerificationExtension;
30+
import org.springframework.test.context.junit.jupiter.SpringExtension;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
import static org.mockito.BDDMockito.given;
34+
35+
/**
36+
* Integration tests for {@link MockitoBean @MockitoBean} fields with different
37+
* {@link MockReset} strategies.
38+
*
39+
* @author Sam Brannen
40+
* @since 6.2.1
41+
* @see MockitoResetTestExecutionListenerWithoutMockitoAnnotationsIntegrationTests
42+
* @see MockitoResetTestExecutionListenerWithMockitoBeanIntegrationTests
43+
*/
44+
// The MockVerificationExtension MUST be registered before the SpringExtension.
45+
@ExtendWith(MockVerificationExtension.class)
46+
@ExtendWith(SpringExtension.class)
47+
@TestMethodOrder(MethodOrderer.MethodName.class)
48+
class MockResetStrategiesIntegrationTests {
49+
50+
static PuzzleService puzzleServiceNoneStaticReference;
51+
static PuzzleService puzzleServiceBeforeStaticReference;
52+
static PuzzleService puzzleServiceAfterStaticReference;
53+
54+
55+
@MockitoBean(name = "puzzleServiceNone", reset = MockReset.NONE)
56+
PuzzleService puzzleServiceNone;
57+
58+
@MockitoBean(name = "puzzleServiceBefore", reset = MockReset.BEFORE)
59+
PuzzleService puzzleServiceBefore;
60+
61+
@MockitoBean(name = "puzzleServiceAfter", reset = MockReset.AFTER)
62+
PuzzleService puzzleServiceAfter;
63+
64+
65+
@AfterEach
66+
void trackStaticReferences() {
67+
puzzleServiceNoneStaticReference = this.puzzleServiceNone;
68+
puzzleServiceBeforeStaticReference = this.puzzleServiceBefore;
69+
puzzleServiceAfterStaticReference = this.puzzleServiceAfter;
70+
}
71+
72+
@AfterAll
73+
static void releaseStaticReferences() {
74+
puzzleServiceNoneStaticReference = null;
75+
puzzleServiceBeforeStaticReference = null;
76+
puzzleServiceAfterStaticReference = null;
77+
}
78+
79+
80+
@Test
81+
void test001(TestInfo testInfo) {
82+
assertThat(puzzleServiceNone.getAnswer()).isNull();
83+
assertThat(puzzleServiceBefore.getAnswer()).isNull();
84+
assertThat(puzzleServiceAfter.getAnswer()).isNull();
85+
86+
stubAndTestMocks(testInfo);
87+
}
88+
89+
@Test
90+
void test002(TestInfo testInfo) {
91+
// Should not have been reset.
92+
assertThat(puzzleServiceNone.getAnswer()).isEqualTo("none - test001");
93+
94+
// Should have been reset.
95+
assertThat(puzzleServiceBefore.getAnswer()).isNull();
96+
assertThat(puzzleServiceAfter.getAnswer()).isNull();
97+
98+
stubAndTestMocks(testInfo);
99+
}
100+
101+
private void stubAndTestMocks(TestInfo testInfo) {
102+
String name = testInfo.getTestMethod().get().getName();
103+
given(puzzleServiceNone.getAnswer()).willReturn("none - " + name);
104+
assertThat(puzzleServiceNone.getAnswer()).isEqualTo("none - " + name);
105+
106+
given(puzzleServiceBefore.getAnswer()).willReturn("before - " + name);
107+
assertThat(puzzleServiceBefore.getAnswer()).isEqualTo("before - " + name);
108+
109+
given(puzzleServiceAfter.getAnswer()).willReturn("after - " + name);
110+
assertThat(puzzleServiceAfter.getAnswer()).isEqualTo("after - " + name);
111+
}
112+
113+
interface PuzzleService {
114+
115+
String getAnswer();
116+
}
117+
118+
static class MockVerificationExtension implements AfterEachCallback {
119+
120+
@Override
121+
public void afterEach(ExtensionContext context) throws Exception {
122+
String name = context.getRequiredTestMethod().getName();
123+
124+
// Should not have been reset.
125+
assertThat(puzzleServiceNoneStaticReference.getAnswer()).as("puzzleServiceNone").isEqualTo("none - " + name);
126+
assertThat(puzzleServiceBeforeStaticReference.getAnswer()).as("puzzleServiceBefore").isEqualTo("before - " + name);
127+
128+
// Should have been reset.
129+
assertThat(puzzleServiceAfterStaticReference.getAnswer()).as("puzzleServiceAfter").isNull();
130+
}
131+
}
132+
133+
}

spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListenerWithMockitoBeanIntegrationTests.java

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* @author Sam Brannen
2929
* @since 6.2
3030
* @see MockitoResetTestExecutionListenerWithoutMockitoAnnotationsIntegrationTests
31+
* @see MockResetStrategiesIntegrationTests
3132
*/
3233
class MockitoResetTestExecutionListenerWithMockitoBeanIntegrationTests
3334
extends MockitoResetTestExecutionListenerWithoutMockitoAnnotationsIntegrationTests {

spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListenerWithoutMockitoAnnotationsIntegrationTests.java

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* @author Sam Brannen
4343
* @since 6.2
4444
* @see MockitoResetTestExecutionListenerWithMockitoBeanIntegrationTests
45+
* @see MockResetStrategiesIntegrationTests
4546
*/
4647
@SpringJUnitConfig
4748
@TestMethodOrder(MethodOrderer.MethodName.class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.bean.override.mockito.integration;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.context.event.ContextRefreshedEvent;
22+
import org.springframework.context.event.EventListener;
23+
import org.springframework.stereotype.Component;
24+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
25+
import org.springframework.test.context.bean.override.mockito.integration.MockitoBeanUsedDuringApplicationContextRefreshIntegrationTests.ContextRefreshedEventListener;
26+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
27+
28+
import static org.mockito.ArgumentMatchers.any;
29+
import static org.mockito.BDDMockito.then;
30+
31+
/**
32+
* Integration tests for {@link MockitoBean @MockitoBean} used during
33+
* {@code ApplicationContext} refresh.
34+
*
35+
* @author Sam Brannen
36+
* @author Yanming Zhou
37+
* @since 6.2.1
38+
*/
39+
@SpringJUnitConfig(ContextRefreshedEventListener.class)
40+
class MockitoBeanUsedDuringApplicationContextRefreshIntegrationTests {
41+
42+
@MockitoBean
43+
ContextRefreshedEventProcessor eventProcessor;
44+
45+
46+
@Test
47+
void test() {
48+
// Ensure that the mock was invoked during ApplicationContext refresh
49+
// and has not been reset in the interim.
50+
then(eventProcessor).should().process(any(ContextRefreshedEvent.class));
51+
}
52+
53+
54+
interface ContextRefreshedEventProcessor {
55+
void process(ContextRefreshedEvent event);
56+
}
57+
58+
// MUST be annotated with @Component, due to EventListenerMethodProcessor.isSpringContainerClass().
59+
@Component
60+
record ContextRefreshedEventListener(ContextRefreshedEventProcessor contextRefreshedEventProcessor) {
61+
62+
@EventListener
63+
void onApplicationEvent(ContextRefreshedEvent event) {
64+
this.contextRefreshedEventProcessor.process(event);
65+
}
66+
}
67+
68+
}

0 commit comments

Comments
 (0)