Skip to content

Commit

Permalink
Honor MockReset strategy for @⁠MockitoBean and @⁠MockitoSpyBean
Browse files Browse the repository at this point in the history
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 spring-projectsgh-33941
  • Loading branch information
sbrannen committed Nov 26, 2024
1 parent 2b840ee commit 0088b9c
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,10 @@ public static MockSettings apply(MockReset reset, MockSettings settings) {
}

/**
* 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,18 @@ void add(Object bean) {
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);
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ private static void resetMocks(ConfigurableApplicationContext applicationContext
}
}
try {
beanFactory.getBean(MockitoBeans.class).resetAll();
beanFactory.getBean(MockitoBeans.class).resetAll(reset);
}
catch (NoSuchBeanDefinitionException ex) {
// Continue
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* @author Sam Brannen
* @since 6.2
* @see MockitoResetTestExecutionListenerWithoutMockitoAnnotationsIntegrationTests
* @see MockResetStrategiesIntegrationTests
*/
class MockitoResetTestExecutionListenerWithMockitoBeanIntegrationTests
extends MockitoResetTestExecutionListenerWithoutMockitoAnnotationsIntegrationTests {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
* @author Sam Brannen
* @since 6.2
* @see MockitoResetTestExecutionListenerWithMockitoBeanIntegrationTests
* @see MockResetStrategiesIntegrationTests
*/
@SpringJUnitConfig
@TestMethodOrder(MethodOrderer.MethodName.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

}

0 comments on commit 0088b9c

Please sign in to comment.