Honor MockReset strategy for @⁠MockitoBean and @⁠MockitoSpyBean

Commit 6c2cba5d8a 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
This commit is contained in:
Sam Brannen 2024-11-26 11:54:42 +01:00
parent 2b840ee7ef
commit 0088b9c7f8
7 changed files with 220 additions and 6 deletions

View File

@ -99,9 +99,10 @@ public enum MockReset {
}
/**
* Get the {@link MockReset} associated with the given mock.
* @param mock the source mock
* @return the reset type (never {@code null})
* Get the {@link MockReset} strategy associated with the given mock.
* @param mock the mock
* @return the reset strategy for the given mock, or {@link MockReset#NONE}
* if no strategy is associated with the given mock
*/
static MockReset get(Object mock) {
MockingDetails mockingDetails = Mockito.mockingDetails(mock);

View File

@ -37,8 +37,18 @@ class MockitoBeans {
this.beans.add(bean);
}
void resetAll() {
this.beans.forEach(Mockito::reset);
/**
* Reset all Mockito beans configured with the supplied {@link MockReset} strategy.
* <p>No mocks will be reset if the supplied strategy is {@link MockReset#NONE}.
*/
void resetAll(MockReset reset) {
if (reset != MockReset.NONE) {
for (Object bean : this.beans) {
if (reset == MockReset.get(bean)) {
Mockito.reset(bean);
}
}
}
}
}

View File

@ -116,7 +116,7 @@ public class MockitoResetTestExecutionListener extends AbstractTestExecutionList
}
}
try {
beanFactory.getBean(MockitoBeans.class).resetAll();
beanFactory.getBean(MockitoBeans.class).resetAll(reset);
}
catch (NoSuchBeanDefinitionException ex) {
// Continue

View File

@ -0,0 +1,133 @@
/*
* Copyright 2002-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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.bean.override.mockito;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.test.context.bean.override.mockito.MockResetStrategiesIntegrationTests.MockVerificationExtension;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Integration tests for {@link MockitoBean @MockitoBean} fields with different
* {@link MockReset} strategies.
*
* @author Sam Brannen
* @since 6.2.1
* @see MockitoResetTestExecutionListenerWithoutMockitoAnnotationsIntegrationTests
* @see MockitoResetTestExecutionListenerWithMockitoBeanIntegrationTests
*/
// The MockVerificationExtension MUST be registered before the SpringExtension.
@ExtendWith(MockVerificationExtension.class)
@ExtendWith(SpringExtension.class)
@TestMethodOrder(MethodOrderer.MethodName.class)
class MockResetStrategiesIntegrationTests {
static PuzzleService puzzleServiceNoneStaticReference;
static PuzzleService puzzleServiceBeforeStaticReference;
static PuzzleService puzzleServiceAfterStaticReference;
@MockitoBean(name = "puzzleServiceNone", reset = MockReset.NONE)
PuzzleService puzzleServiceNone;
@MockitoBean(name = "puzzleServiceBefore", reset = MockReset.BEFORE)
PuzzleService puzzleServiceBefore;
@MockitoBean(name = "puzzleServiceAfter", reset = MockReset.AFTER)
PuzzleService puzzleServiceAfter;
@AfterEach
void trackStaticReferences() {
puzzleServiceNoneStaticReference = this.puzzleServiceNone;
puzzleServiceBeforeStaticReference = this.puzzleServiceBefore;
puzzleServiceAfterStaticReference = this.puzzleServiceAfter;
}
@AfterAll
static void releaseStaticReferences() {
puzzleServiceNoneStaticReference = null;
puzzleServiceBeforeStaticReference = null;
puzzleServiceAfterStaticReference = null;
}
@Test
void test001(TestInfo testInfo) {
assertThat(puzzleServiceNone.getAnswer()).isNull();
assertThat(puzzleServiceBefore.getAnswer()).isNull();
assertThat(puzzleServiceAfter.getAnswer()).isNull();
stubAndTestMocks(testInfo);
}
@Test
void test002(TestInfo testInfo) {
// Should not have been reset.
assertThat(puzzleServiceNone.getAnswer()).isEqualTo("none - test001");
// Should have been reset.
assertThat(puzzleServiceBefore.getAnswer()).isNull();
assertThat(puzzleServiceAfter.getAnswer()).isNull();
stubAndTestMocks(testInfo);
}
private void stubAndTestMocks(TestInfo testInfo) {
String name = testInfo.getTestMethod().get().getName();
given(puzzleServiceNone.getAnswer()).willReturn("none - " + name);
assertThat(puzzleServiceNone.getAnswer()).isEqualTo("none - " + name);
given(puzzleServiceBefore.getAnswer()).willReturn("before - " + name);
assertThat(puzzleServiceBefore.getAnswer()).isEqualTo("before - " + name);
given(puzzleServiceAfter.getAnswer()).willReturn("after - " + name);
assertThat(puzzleServiceAfter.getAnswer()).isEqualTo("after - " + name);
}
interface PuzzleService {
String getAnswer();
}
static class MockVerificationExtension implements AfterEachCallback {
@Override
public void afterEach(ExtensionContext context) throws Exception {
String name = context.getRequiredTestMethod().getName();
// Should not have been reset.
assertThat(puzzleServiceNoneStaticReference.getAnswer()).as("puzzleServiceNone").isEqualTo("none - " + name);
assertThat(puzzleServiceBeforeStaticReference.getAnswer()).as("puzzleServiceBefore").isEqualTo("before - " + name);
// Should have been reset.
assertThat(puzzleServiceAfterStaticReference.getAnswer()).as("puzzleServiceAfter").isNull();
}
}
}

View File

@ -28,6 +28,7 @@ import static org.mockito.BDDMockito.given;
* @author Sam Brannen
* @since 6.2
* @see MockitoResetTestExecutionListenerWithoutMockitoAnnotationsIntegrationTests
* @see MockResetStrategiesIntegrationTests
*/
class MockitoResetTestExecutionListenerWithMockitoBeanIntegrationTests
extends MockitoResetTestExecutionListenerWithoutMockitoAnnotationsIntegrationTests {

View File

@ -42,6 +42,7 @@ import static org.mockito.Mockito.mock;
* @author Sam Brannen
* @since 6.2
* @see MockitoResetTestExecutionListenerWithMockitoBeanIntegrationTests
* @see MockResetStrategiesIntegrationTests
*/
@SpringJUnitConfig
@TestMethodOrder(MethodOrderer.MethodName.class)

View File

@ -0,0 +1,68 @@
/*
* Copyright 2002-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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.bean.override.mockito.integration;
import org.junit.jupiter.api.Test;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.bean.override.mockito.integration.MockitoBeanUsedDuringApplicationContextRefreshIntegrationTests.ContextRefreshedEventListener;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
/**
* Integration tests for {@link MockitoBean @MockitoBean} used during
* {@code ApplicationContext} refresh.
*
* @author Sam Brannen
* @author Yanming Zhou
* @since 6.2.1
*/
@SpringJUnitConfig(ContextRefreshedEventListener.class)
class MockitoBeanUsedDuringApplicationContextRefreshIntegrationTests {
@MockitoBean
ContextRefreshedEventProcessor eventProcessor;
@Test
void test() {
// Ensure that the mock was invoked during ApplicationContext refresh
// and has not been reset in the interim.
then(eventProcessor).should().process(any(ContextRefreshedEvent.class));
}
interface ContextRefreshedEventProcessor {
void process(ContextRefreshedEvent event);
}
// MUST be annotated with @Component, due to EventListenerMethodProcessor.isSpringContainerClass().
@Component
record ContextRefreshedEventListener(ContextRefreshedEventProcessor contextRefreshedEventProcessor) {
@EventListener
void onApplicationEvent(ContextRefreshedEvent event) {
this.contextRefreshedEventProcessor.process(event);
}
}
}