Browse Source
Prior to this commit, a developer may have accidentally annotated a JUnit Jupiter test method or lifecycle method with @Autowired, and that would have potentially resulted in an exception that was hard to understand. This is because the Spring container considers any @Autowired method to be a "configuration method" when autowiring the test class instance. Consequently, such an @Autowired method would be invoked twice: once by Spring while attempting to autowire the test instance and another time by JUnit Jupiter when invoking the test or lifecycle method. The autowiring invocation of the method often leads to an exception, either because Spring cannot satisfy a dependency (such as JUnit Jupiter's TestInfo) or because the body of the method fails due to test setup that has not yet been invoked. This commit introduces validation for @Autowired test and lifecycle methods in the SpringExtension that will throw an IllegalStateException if any @Autowired method in a test class is also annotated with any of the following JUnit Jupiter annotations. - @Test - @TestFactory - @TestTemplate - @RepeatedTest - @ParameterizedTest - @BeforeAll - @AfterAll - @BeforeEach - @AfterEach Closes gh-25966pull/26070/head
2 changed files with 372 additions and 4 deletions
@ -0,0 +1,290 @@
@@ -0,0 +1,290 @@
|
||||
/* |
||||
* Copyright 2002-2020 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.junit.jupiter; |
||||
|
||||
import java.util.stream.Stream; |
||||
|
||||
import org.junit.jupiter.api.AfterAll; |
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeAll; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.DynamicTest; |
||||
import org.junit.jupiter.api.RepeatedTest; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.TestFactory; |
||||
import org.junit.jupiter.api.TestInfo; |
||||
import org.junit.jupiter.api.TestInstance; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.ValueSource; |
||||
import org.junit.platform.testkit.engine.EngineTestKit; |
||||
import org.junit.platform.testkit.engine.Events; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest; |
||||
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; |
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; |
||||
import static org.junit.platform.testkit.engine.EventConditions.container; |
||||
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; |
||||
|
||||
/** |
||||
* Integration tests for {@link Autowired @Autowired} configuration errors in |
||||
* JUnit Jupiter test classes. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 5.3.2 |
||||
*/ |
||||
class AutowiredConfigurationErrorsIntegrationTests { |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(classes = { |
||||
StaticAutowiredBeforeAllMethod.class, |
||||
StaticAutowiredAfterAllMethod.class, |
||||
AutowiredBeforeEachMethod.class, |
||||
AutowiredAfterEachMethod.class, |
||||
AutowiredTestMethod.class, |
||||
AutowiredRepeatedTestMethod.class, |
||||
AutowiredParameterizedTestMethod.class |
||||
}) |
||||
void autowiredTestMethodsTestTemplateMethodsAndLifecyleMethods(Class<?> testClass) { |
||||
testEventsFor(testClass) |
||||
.assertStatistics(stats -> stats.started(1).succeeded(0).failed(1)) |
||||
.assertThatEvents().haveExactly(1, |
||||
event(test("test"), |
||||
finishedWithFailure( |
||||
instanceOf(IllegalStateException.class), |
||||
message(msg -> msg.matches(".+must not be annotated with @Autowired.+"))))); |
||||
} |
||||
|
||||
/** |
||||
* A non-autowired test method should fail the same as an autowired test |
||||
* method in the same class, since Spring still should not autowire the |
||||
* autowired test method as a "configuration method" when JUnit attempts to |
||||
* execute the non-autowired test method. |
||||
*/ |
||||
@Test |
||||
void autowiredAndNonAutowiredTestMethods() { |
||||
testEventsFor(AutowiredAndNonAutowiredTestMethods.class) |
||||
.assertStatistics(stats -> stats.started(2).succeeded(0).failed(2)) |
||||
.assertThatEvents() |
||||
.haveExactly(1, |
||||
event(test("autowired"), |
||||
finishedWithFailure( |
||||
instanceOf(IllegalStateException.class), |
||||
message(msg -> msg.matches(".+must not be annotated with @Autowired.+"))))) |
||||
.haveExactly(1, |
||||
event(test("nonAutowired"), |
||||
finishedWithFailure( |
||||
instanceOf(IllegalStateException.class), |
||||
message(msg -> msg.matches(".+must not be annotated with @Autowired.+"))))); |
||||
} |
||||
|
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(classes = { |
||||
NonStaticAutowiredBeforeAllMethod.class, |
||||
NonStaticAutowiredAfterAllMethod.class |
||||
}) |
||||
void autowiredNonStaticClassLevelLifecyleMethods(Class<?> testClass) { |
||||
containerEventsFor(testClass) |
||||
.assertStatistics(stats -> stats.started(2).succeeded(1).failed(1)) |
||||
.assertThatEvents().haveExactly(1, |
||||
event(container(), |
||||
finishedWithFailure( |
||||
instanceOf(IllegalStateException.class), |
||||
message(msg -> msg.matches(".+must not be annotated with @Autowired.+"))))); |
||||
} |
||||
|
||||
@Test |
||||
void autowiredTestFactoryMethod() { |
||||
containerEventsFor(AutowiredTestFactoryMethod.class) |
||||
.assertStatistics(stats -> stats.started(3).succeeded(2).failed(1)) |
||||
.assertThatEvents().haveExactly(1, |
||||
event(container(), |
||||
finishedWithFailure( |
||||
instanceOf(IllegalStateException.class), |
||||
message(msg -> msg.matches(".+must not be annotated with @Autowired.+"))))); |
||||
} |
||||
|
||||
private Events testEventsFor(Class<?> testClass) { |
||||
return EngineTestKit.engine("junit-jupiter") |
||||
.selectors(selectClass(testClass)) |
||||
.execute() |
||||
.testEvents(); |
||||
} |
||||
|
||||
private Events containerEventsFor(Class<?> testClass) { |
||||
return EngineTestKit.engine("junit-jupiter") |
||||
.selectors(selectClass(testClass)) |
||||
.execute() |
||||
.containerEvents(); |
||||
} |
||||
|
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@FailingTestCase |
||||
static class StaticAutowiredBeforeAllMethod { |
||||
|
||||
@Autowired |
||||
@BeforeAll |
||||
static void beforeAll(TestInfo testInfo) { |
||||
} |
||||
|
||||
@Test |
||||
void test() { |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@TestInstance(PER_CLASS) |
||||
@FailingTestCase |
||||
static class NonStaticAutowiredBeforeAllMethod { |
||||
|
||||
@Autowired |
||||
@BeforeAll |
||||
void beforeAll(TestInfo testInfo) { |
||||
} |
||||
|
||||
@Test |
||||
void test() { |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@FailingTestCase |
||||
static class StaticAutowiredAfterAllMethod { |
||||
|
||||
@Test |
||||
void test() { |
||||
} |
||||
|
||||
@AfterAll |
||||
@Autowired |
||||
static void afterAll(TestInfo testInfo) { |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@TestInstance(PER_CLASS) |
||||
@FailingTestCase |
||||
static class NonStaticAutowiredAfterAllMethod { |
||||
|
||||
@Test |
||||
void test() { |
||||
} |
||||
|
||||
@AfterAll |
||||
@Autowired |
||||
void afterAll(TestInfo testInfo) { |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@FailingTestCase |
||||
static class AutowiredBeforeEachMethod { |
||||
|
||||
@Autowired |
||||
@BeforeEach |
||||
void beforeEach(TestInfo testInfo) { |
||||
} |
||||
|
||||
@Test |
||||
void test() { |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@FailingTestCase |
||||
static class AutowiredAfterEachMethod { |
||||
|
||||
@Test |
||||
void test() { |
||||
} |
||||
|
||||
@Autowired |
||||
@AfterEach |
||||
void afterEach(TestInfo testInfo) { |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@FailingTestCase |
||||
static class AutowiredTestMethod { |
||||
|
||||
@Autowired |
||||
@Test |
||||
void test(TestInfo testInfo) { |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@FailingTestCase |
||||
static class AutowiredAndNonAutowiredTestMethods { |
||||
|
||||
@Autowired |
||||
@Test |
||||
void autowired(TestInfo testInfo) { |
||||
} |
||||
|
||||
@Test |
||||
void nonAutowired(TestInfo testInfo) { |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@FailingTestCase |
||||
static class AutowiredRepeatedTestMethod { |
||||
|
||||
@Autowired |
||||
@RepeatedTest(1) |
||||
void repeatedTest(TestInfo testInfo) { |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@FailingTestCase |
||||
static class AutowiredTestFactoryMethod { |
||||
|
||||
@Autowired |
||||
@TestFactory |
||||
Stream<DynamicTest> testFactory(TestInfo testInfo) { |
||||
return Stream.of(dynamicTest("dynamicTest", () -> {})); |
||||
} |
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@FailingTestCase |
||||
static class AutowiredParameterizedTestMethod { |
||||
|
||||
@Autowired |
||||
@ParameterizedTest |
||||
@ValueSource(strings = "ignored") |
||||
void parameterizedTest(TestInfo testInfo) { |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class Config { |
||||
} |
||||
|
||||
} |
||||
|
Loading…
Reference in new issue