mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:30:28 +00:00
Reset mocks only when a test has an ApplicationContext
Prior to this commit and the previous commit, MockitoResetTestExecutionListener always attempted to load the ApplicationContext to reset mocks in its beforeTestMethod() and afterTestMethod() callbacks, even if there was no active ApplicationContext. The reason this was noticed is that the @BeforeMethod(alwaysRun = true) and @AfterMethod(alwaysRun = true) lifecycle methods in AbstractTestNGSpringContextTests are always invoked, even if a previous lifecycle configuration method failed (for example, due to a context-load failure). However, with JUnit Jupiter and the SpringExtension the beforeTestMethod() and afterTestMethod() callbacks in the TestExecutionListener API are not invoked if there was a previous lifecycle failure. Consequently, the reported drawbacks only exist when using Spring's TestNG base support classes This commit picks up where the previous commit left off by applying the same hasApplicationContext() check in beforeTestMethod(). This commit also introduces unit and integration tests for both Jupiter and TestNG support. Closes gh-36782
This commit is contained in:
+1
-1
@@ -92,7 +92,7 @@ public class MockitoResetTestExecutionListener extends AbstractTestExecutionList
|
||||
|
||||
@Override
|
||||
public void beforeTestMethod(TestContext testContext) {
|
||||
if (isEnabled()) {
|
||||
if (isEnabled() && testContext.hasApplicationContext()) {
|
||||
resetMocks(testContext.getApplicationContext(), MockReset.BEFORE);
|
||||
}
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.Test;
|
||||
|
||||
import org.springframework.test.context.TestContext;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MockitoResetTestExecutionListener}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.1
|
||||
* @see MockitoResetTestExecutionListenerWithContextLoadFailureTests
|
||||
* @see MockitoResetTestExecutionListenerWithContextLoadFailureTestNGTests
|
||||
*/
|
||||
class MockitoResetTestExecutionListenerTests {
|
||||
|
||||
private final TestContext testContext = mock();
|
||||
|
||||
|
||||
@Test
|
||||
void beforeTestMethodIsNoOpWhenContextIsNotAvailable() {
|
||||
when(testContext.hasApplicationContext()).thenReturn(false);
|
||||
|
||||
new MockitoResetTestExecutionListener().beforeTestMethod(testContext);
|
||||
|
||||
verify(testContext).hasApplicationContext();
|
||||
verify(testContext, never()).getApplicationContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void afterTestMethodIsNoOpWhenContextIsNotAvailable() {
|
||||
when(testContext.hasApplicationContext()).thenReturn(false);
|
||||
|
||||
new MockitoResetTestExecutionListener().afterTestMethod(testContext);
|
||||
|
||||
verify(testContext).hasApplicationContext();
|
||||
verify(testContext, never()).getApplicationContext();
|
||||
}
|
||||
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.Test;
|
||||
import org.testng.TestNG;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideTestExecutionListener;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.springframework.test.context.testng.TrackingTestNGTestListener;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* JUnit based integration test which verifies that
|
||||
* {@link MockitoResetTestExecutionListener} — when used in conjunction with
|
||||
* Spring's TestNG support — does not attempt to load an application context
|
||||
* to reset mocks if the application context is not currently loaded or previously
|
||||
* failed to load.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/36782">gh-36782</a>
|
||||
* @see MockitoResetTestExecutionListenerWithContextLoadFailureTests
|
||||
*/
|
||||
class MockitoResetTestExecutionListenerWithContextLoadFailureTestNGTests {
|
||||
|
||||
/**
|
||||
* <p>NOTE: The {@code @BeforeMethod(alwaysRun = true)} and {@code @AfterMethod(alwaysRun = true)}
|
||||
* lifecycle methods in {@link AbstractTestNGSpringContextTests} are always invoked, even if a
|
||||
* previous lifecycle configuration method failed (for example, due to a context-load failure).
|
||||
*/
|
||||
@Test
|
||||
void contextLoadFailureCausesExpectedTestFailures() {
|
||||
TrackingTestNGTestListener listener = new TrackingTestNGTestListener();
|
||||
TestNG testNG = new TestNG();
|
||||
testNG.addListener(listener);
|
||||
testNG.setTestClasses(new Class<?>[] { ContextLoadFailureTestCase.class });
|
||||
testNG.setVerbose(0);
|
||||
testNG.run();
|
||||
|
||||
assertThat(listener.testStartCount).as("tests started").hasValue(2);
|
||||
assertThat(listener.testSuccessCount).as("tests succeeded").hasValue(0);
|
||||
assertThat(listener.testFailureCount).as("tests failed").hasValue(0);
|
||||
// Before the introduction of hasApplicationContext() checks in
|
||||
// MockitoResetTestExecutionListener, the @BeforeMethod and @AfterMethod
|
||||
// lifecycle methods in AbstractTestNGSpringContextTests also attempted to
|
||||
// load the faulty ApplicationContext, resulting in 5 configuration failures:
|
||||
// 1 * @BeforeClass + 2 * @BeforeMethod + 2 * @AfterMethod = 5.
|
||||
// With the fix, only the @BeforeClass context-load failure is recorded.
|
||||
assertThat(listener.failedConfigurationsCount).as("failed configurations").hasValue(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>The {@code @TestExecutionListeners} declaration replaces the default listeners with
|
||||
* only those needed to exercise {@link MockitoResetTestExecutionListener}, ensuring that
|
||||
* no additional listeners call {@code testContext.getApplicationContext()} without a
|
||||
* conditional {@code hasApplicationContext()} check.
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@TestExecutionListeners({
|
||||
BeanOverrideTestExecutionListener.class,
|
||||
DependencyInjectionTestExecutionListener.class,
|
||||
MockitoResetTestExecutionListener.class
|
||||
})
|
||||
static class ContextLoadFailureTestCase extends AbstractTestNGSpringContextTests {
|
||||
|
||||
@MockitoBean
|
||||
ExampleService exampleService;
|
||||
|
||||
@org.testng.annotations.Test
|
||||
void test1() {
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
void test2() {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
String alwaysFails() {
|
||||
throw new RuntimeException("Simulated context load failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.MethodOrderer.MethodName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideTestExecutionListener;
|
||||
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
import static org.junit.platform.testkit.engine.EventConditions.event;
|
||||
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
|
||||
import static org.junit.platform.testkit.engine.EventConditions.test;
|
||||
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
|
||||
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
|
||||
|
||||
/**
|
||||
* Tests which indirectly verify that {@link MockitoResetTestExecutionListener} does not
|
||||
* attempt to load an application context to reset mocks if the application context
|
||||
* is not currently loaded or previously failed to load.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/36782">gh-36782</a>
|
||||
* @see MockitoResetTestExecutionListenerTests
|
||||
* @see MockitoResetTestExecutionListenerWithContextLoadFailureTestNGTests
|
||||
*/
|
||||
class MockitoResetTestExecutionListenerWithContextLoadFailureTests {
|
||||
|
||||
@Test
|
||||
void contextLoadFailureCausesExpectedTestFailures() {
|
||||
EngineTestKit.engine("junit-jupiter")
|
||||
.selectors(selectClass(ContextLoadFailureTestCase.class))
|
||||
.execute()
|
||||
.testEvents()
|
||||
.assertStatistics(stats -> stats.started(2).succeeded(0).failed(2))
|
||||
.assertThatEvents()
|
||||
.haveExactly(1, event(test("test1"),
|
||||
finishedWithFailure(
|
||||
instanceOf(IllegalStateException.class),
|
||||
message(msg -> msg.startsWith("Failed to load ApplicationContext")))))
|
||||
.haveExactly(1, event(test("test2"),
|
||||
finishedWithFailure(
|
||||
instanceOf(IllegalStateException.class),
|
||||
message(msg -> msg.contains("failure threshold")))));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>The {@code @TestExecutionListeners} declaration replaces the default listeners with
|
||||
* only those needed to exercise {@link MockitoResetTestExecutionListener}, ensuring that
|
||||
* no additional listeners call {@code testContext.getApplicationContext()} without a
|
||||
* conditional {@code hasApplicationContext()} check.
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@TestExecutionListeners({
|
||||
BeanOverrideTestExecutionListener.class,
|
||||
DependencyInjectionTestExecutionListener.class,
|
||||
MockitoResetTestExecutionListener.class
|
||||
})
|
||||
@TestMethodOrder(MethodName.class)
|
||||
static class ContextLoadFailureTestCase {
|
||||
|
||||
@MockitoBean
|
||||
ExampleService exampleService;
|
||||
|
||||
@Test
|
||||
void test1() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2() {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
String alwaysFails() {
|
||||
throw new RuntimeException("Simulated context load failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user