diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoTestExecutionListener.java index 5648657b69..8053f27146 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoTestExecutionListener.java @@ -20,8 +20,8 @@ import org.mockito.Mockito; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; /** @@ -80,7 +80,8 @@ public class MockitoTestExecutionListener extends AbstractMockitoTestExecutionLi private static void initMocks(TestContext testContext) { Class testClass = testContext.getTestClass(); Object testInstance = testContext.getTestInstance(); - MockitoBeanSettings annotation = AnnotationUtils.findAnnotation(testClass, MockitoBeanSettings.class); + MockitoBeanSettings annotation = + TestContextAnnotationUtils.findMergedAnnotation(testClass, MockitoBeanSettings.class); Strictness strictness = (annotation != null ? annotation.value() : Strictness.STRICT_STUBS); testContext.setAttribute(MOCKITO_SESSION_ATTRIBUTE_NAME, initMockitoSession(testInstance, strictness)); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsInheritedStrictnessTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsInheritedStrictnessTests.java new file mode 100644 index 0000000000..752c01d908 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsInheritedStrictnessTests.java @@ -0,0 +1,63 @@ +/* + * 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 java.util.List; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.quality.Strictness; + +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.BDDMockito.when; +import static org.mockito.Mockito.mock; + +/** + * Tests which verify that strictness configured via + * {@link MockitoBeanSettings @MockitoBeanSettings} is inherited in + * {@link Nested @Nested} test classes. + * + * @author Sam Brannen + * @since 6.2 + */ +@ExtendWith(SpringExtension.class) +@TestExecutionListeners(MockitoTestExecutionListener.class) +@MockitoBeanSettings(Strictness.LENIENT) +class MockitoBeanSettingsInheritedStrictnessTests { + + // Should inherit Strictness.LENIENT. + @Nested + class NestedTests { + + @Test + @SuppressWarnings("rawtypes") + void unnecessaryStub() { + List list = mock(); + when(list.get(anyInt())).thenReturn("enigma"); + + // We intentionally do NOT perform any assertions against the mock, + // because we want to ensure that an UnnecessaryStubbingException is + // not thrown by Mockito. + // assertThat(list.get(1)).isEqualTo("enigma"); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsStrictIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsStrictIntegrationTests.java index 2f998b5359..d74685ccc0 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsStrictIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsStrictIntegrationTests.java @@ -16,9 +16,9 @@ package org.springframework.test.context.bean.override.mockito; -import java.time.format.DateTimeFormatter; import java.util.List; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -41,6 +41,7 @@ import static org.junit.platform.testkit.engine.TestExecutionResultConditions.me import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.BDDMockito.when; import static org.mockito.Mockito.mock; +import static org.mockito.quality.Strictness.LENIENT; import static org.mockito.quality.Strictness.STRICT_STUBS; /** @@ -48,20 +49,21 @@ import static org.mockito.quality.Strictness.STRICT_STUBS; * cases where a strict style is chosen or assumed. * * @author Simon Baslé + * @author Sam Brannen * @since 6.2 */ class MockitoBeanSettingsStrictIntegrationTests { @ParameterizedTest @FieldSource("strictCases") - void unusedStubbingIsReported(Class forCase) { + void unusedStubbingIsReported(Class testCase, int startedCount, int failureCount) { Events events = EngineTestKit.engine("junit-jupiter") - .selectors(selectClass(forCase)) + .selectors(selectClass(testCase)) .execute() .testEvents() - .assertStatistics(stats -> stats.started(1).failed(1)); + .assertStatistics(stats -> stats.started(startedCount).failed(failureCount)); - events.assertThatEvents().haveExactly(1, + events.assertThatEvents().haveExactly(failureCount, event(test("unnecessaryStub"), finishedWithFailure( instanceOf(UnnecessaryStubbingException.class), @@ -69,8 +71,13 @@ class MockitoBeanSettingsStrictIntegrationTests { } static final List strictCases = List.of( - argumentSet("explicit strictness", ExplicitStrictness.class), - argumentSet("implicit strictness with @MockitoBean on field", ImplicitStrictnessWithMockitoBean.class) + argumentSet("explicit strictness", ExplicitStrictness.class, 1, 1), + argumentSet("explicit strictness on enclosing class", ExplicitStrictnessEnclosingTestCase.class, 1, 1), + argumentSet("implicit strictness with @MockitoBean on field", ImplicitStrictnessWithMockitoBean.class, 1, 1), + // 3, 1 --> The tests in LenientStubbingNestedTestCase and InheritedLenientStubbingNestedTestCase + // should not result in an UnnecessaryStubbingException. + argumentSet("implicit strictness overridden and inherited in @Nested test classes", + ImplicitStrictnessWithMockitoBeanEnclosingTestCase.class, 3, 1) ); abstract static class BaseCase { @@ -94,8 +101,36 @@ class MockitoBeanSettingsStrictIntegrationTests { static class ImplicitStrictnessWithMockitoBean extends BaseCase { @MockitoBean - @SuppressWarnings("unused") - DateTimeFormatter ignoredMock; + Runnable ignoredMock; + } + + @SpringJUnitConfig(Config.class) + @DirtiesContext + @MockitoBeanSettings(STRICT_STUBS) + static class ExplicitStrictnessEnclosingTestCase { + + @Nested + class NestedTestCase extends BaseCase { + } + } + + @SpringJUnitConfig(Config.class) + @DirtiesContext + static class ImplicitStrictnessWithMockitoBeanEnclosingTestCase extends BaseCase { + + @MockitoBean + Runnable ignoredMock; + + @Nested + // Overrides implicit STRICT_STUBS + @MockitoBeanSettings(LENIENT) + class LenientStubbingNestedTestCase extends BaseCase { + + @Nested + // Inherits LENIENT + class InheritedLenientStubbingNestedTestCase extends BaseCase { + } + } } @Configuration(proxyBeanMethods = false)