Skip to content

Commit 873fc53

Browse files
committed
Introduce support for JUnit 5 in the TestContext framework
This commit introduces initial support for JUnit Jupiter (i.e., the new programming and extension models in JUnit 5) in the Spring TestContext Framework. Specifically, this commit introduces the following. - SpringExtension: an implementation of multiple extension APIs from JUnit Jupiter that provides full support for the existing feature set of the Spring TestContext Framework. This support is enabled via @ExtendWith(SpringExtension.class). - @SpringJUnitConfig: a composed annotation that combines @ExtendWith(SpringExtension.class) from JUnit Jupiter with @ContextConfiguration from the Spring TestContext Framework. - @SpringJUnitWebConfig: a composed annotation that combines @ExtendWith(SpringExtension.class) from JUnit Jupiter with @ContextConfiguration and @WebAppConfiguration from the Spring TestContext Framework. Issue: SPR-13575
1 parent 54e3ea8 commit 873fc53

29 files changed

+1812
-1
lines changed

build.gradle

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ configure(allprojects) { project ->
6060
ext.jrubyVersion = "1.7.25" // JRuby 9000 only supported through JSR-223 (StandardScriptFactory)
6161
ext.jtaVersion = "1.2"
6262
ext.junitVersion = "4.12"
63+
ext.junitJupiterVersion = '5.0.0-SNAPSHOT'
64+
ext.junitPlatformVersion = '1.0.0-SNAPSHOT'
6365
ext.log4jVersion = "1.2.17"
6466
ext.nettyVersion = "4.1.1.Final"
6567
ext.okhttpVersion = "2.7.5"
@@ -131,6 +133,8 @@ configure(allprojects) { project ->
131133

132134
repositories {
133135
maven { url "https://repo.spring.io/libs-release" }
136+
// For JUnit Platform and Jupiter snapshots:
137+
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
134138
}
135139

136140
dependencies {
@@ -993,6 +997,7 @@ project("spring-test") {
993997
optional(project(":spring-webmvc-portlet"))
994998
optional(project(":spring-websocket"))
995999
optional("junit:junit:${junitVersion}")
1000+
optional("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
9961001
optional("org.testng:testng:${testngVersion}")
9971002
optional("javax.inject:javax.inject:1")
9981003
optional("javax.servlet:javax.servlet-api:3.0.1")
@@ -1020,6 +1025,8 @@ project("spring-test") {
10201025
testCompile("org.hibernate:hibernate-core:${hibernate4Version}")
10211026
testCompile("org.hibernate:hibernate-entitymanager:${hibernate4Version}")
10221027
testCompile("org.hibernate:hibernate-validator:${hibval5Version}")
1028+
// Enable use of the JUnitPlatform Runner
1029+
testCompile("org.junit.platform:junit-platform-runner:${junitPlatformVersion}")
10231030
testCompile("com.thoughtworks.xstream:xstream:${xstreamVersion}")
10241031
testCompile("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
10251032
testCompile("com.rometools:rome:${romeVersion}")
@@ -1034,7 +1041,9 @@ project("spring-test") {
10341041
testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}")
10351042
testCompile("org.apache.httpcomponents:httpclient:${httpclientVersion}")
10361043
testCompile("javax.cache:cache-api:1.0.0")
1044+
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
10371045
testRuntime("log4j:log4j:${log4jVersion}")
1046+
10381047
testRuntime("org.ehcache:ehcache:${ehcache3Version}")
10391048
testRuntime("org.terracotta:management-model:2.0.0")
10401049
}
@@ -1053,7 +1062,9 @@ project("spring-test") {
10531062
description = 'Runs JUnit tests.'
10541063
dependsOn testNG
10551064
useJUnit()
1056-
exclude "**/testng/**/*.*"
1065+
scanForTestClasses = false
1066+
include(['**/*Tests.class', '**/*Test.class', '**/SpringJUnitJupiterTestSuite.class'])
1067+
exclude(['**/testng/**/*.*'])
10571068
}
10581069

10591070
task aggregateTestReports(type: TestReport) {
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.junit.jupiter;
18+
19+
import java.lang.reflect.Constructor;
20+
import java.lang.reflect.Executable;
21+
import java.lang.reflect.Method;
22+
import java.lang.reflect.Parameter;
23+
24+
import org.junit.jupiter.api.extension.AfterAllCallback;
25+
import org.junit.jupiter.api.extension.AfterEachCallback;
26+
import org.junit.jupiter.api.extension.BeforeAllCallback;
27+
import org.junit.jupiter.api.extension.BeforeEachCallback;
28+
import org.junit.jupiter.api.extension.ContainerExtensionContext;
29+
import org.junit.jupiter.api.extension.ExtensionContext;
30+
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
31+
import org.junit.jupiter.api.extension.ExtensionContext.Store;
32+
import org.junit.jupiter.api.extension.ParameterContext;
33+
import org.junit.jupiter.api.extension.ParameterResolver;
34+
import org.junit.jupiter.api.extension.TestExtensionContext;
35+
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
36+
37+
import org.springframework.beans.factory.annotation.Autowired;
38+
import org.springframework.context.ApplicationContext;
39+
import org.springframework.core.annotation.AnnotatedElementUtils;
40+
import org.springframework.test.context.TestContextManager;
41+
import org.springframework.test.context.junit.jupiter.support.ParameterAutowireUtils;
42+
import org.springframework.util.Assert;
43+
44+
/**
45+
* {@code SpringExtension} integrates the <em>Spring TestContext Framework</em>
46+
* into JUnit 5's <em>Jupiter</em> programming model.
47+
*
48+
* <p>To use this class, simply annotate a JUnit Jupiter based test class with
49+
* {@code @ExtendWith(SpringExtension.class)}.
50+
*
51+
* @author Sam Brannen
52+
* @since 5.0
53+
* @see org.springframework.test.context.junit.jupiter.SpringJUnitConfig
54+
* @see org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig
55+
* @see org.springframework.test.context.TestContextManager
56+
*/
57+
public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor,
58+
BeforeEachCallback, AfterEachCallback, ParameterResolver {
59+
60+
/**
61+
* {@link Namespace} in which {@code TestContextManagers} are stored, keyed
62+
* by test class.
63+
*/
64+
private static final Namespace namespace = Namespace.create(SpringExtension.class);
65+
66+
/**
67+
* Delegates to {@link TestContextManager#beforeTestClass}.
68+
*/
69+
@Override
70+
public void beforeAll(ContainerExtensionContext context) throws Exception {
71+
getTestContextManager(context).beforeTestClass();
72+
}
73+
74+
/**
75+
* Delegates to {@link TestContextManager#afterTestClass}.
76+
*/
77+
@Override
78+
public void afterAll(ContainerExtensionContext context) throws Exception {
79+
try {
80+
getTestContextManager(context).afterTestClass();
81+
}
82+
finally {
83+
context.getStore(namespace).remove(context.getTestClass().get());
84+
}
85+
}
86+
87+
/**
88+
* Delegates to {@link TestContextManager#prepareTestInstance}.
89+
*/
90+
@Override
91+
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
92+
getTestContextManager(context).prepareTestInstance(testInstance);
93+
}
94+
95+
/**
96+
* Delegates to {@link TestContextManager#beforeTestMethod}.
97+
*/
98+
@Override
99+
public void beforeEach(TestExtensionContext context) throws Exception {
100+
Object testInstance = context.getTestInstance();
101+
Method testMethod = context.getTestMethod().get();
102+
getTestContextManager(context).beforeTestMethod(testInstance, testMethod);
103+
}
104+
105+
/**
106+
* Delegates to {@link TestContextManager#afterTestMethod}.
107+
*/
108+
@Override
109+
public void afterEach(TestExtensionContext context) throws Exception {
110+
Object testInstance = context.getTestInstance();
111+
Method testMethod = context.getTestMethod().get();
112+
Throwable testException = context.getTestException().orElse(null);
113+
getTestContextManager(context).afterTestMethod(testInstance, testMethod, testException);
114+
}
115+
116+
/**
117+
* Determine if the value for the {@link Parameter} in the supplied
118+
* {@link ParameterContext} should be autowired from the test's
119+
* {@link ApplicationContext}.
120+
* <p>Returns {@code true} if the parameter is declared in a {@link Constructor}
121+
* that is annotated with {@link Autowired @Autowired} and otherwise delegates
122+
* to {@link ParameterAutowireUtils#isAutowirable}.
123+
* <p><strong>WARNING</strong>: if the parameter is declared in a {@code Constructor}
124+
* that is annotated with {@code @Autowired}, Spring will assume the responsibility
125+
* for resolving all parameters in the constructor. Consequently, no other
126+
* registered {@link ParameterResolver} will be able to resolve parameters.
127+
*
128+
* @see #resolve
129+
* @see ParameterAutowireUtils#isAutowirable
130+
*/
131+
@Override
132+
public boolean supports(ParameterContext parameterContext, ExtensionContext extensionContext) {
133+
Parameter parameter = parameterContext.getParameter();
134+
Executable executable = parameter.getDeclaringExecutable();
135+
return (executable instanceof Constructor && AnnotatedElementUtils.hasAnnotation(executable, Autowired.class))
136+
|| ParameterAutowireUtils.isAutowirable(parameter);
137+
}
138+
139+
/**
140+
* Resolve a value for the {@link Parameter} in the supplied
141+
* {@link ParameterContext} by retrieving the corresponding dependency
142+
* from the test's {@link ApplicationContext}.
143+
* <p>Delegates to {@link ParameterAutowireUtils#resolveDependency}.
144+
* @see #supports
145+
* @see ParameterAutowireUtils#resolveDependency
146+
*/
147+
@Override
148+
public Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext) {
149+
Parameter parameter = parameterContext.getParameter();
150+
Class<?> testClass = extensionContext.getTestClass().get();
151+
ApplicationContext applicationContext = getApplicationContext(extensionContext);
152+
return ParameterAutowireUtils.resolveDependency(parameter, testClass, applicationContext);
153+
}
154+
155+
/**
156+
* Get the {@link ApplicationContext} associated with the supplied
157+
* {@code ExtensionContext}.
158+
* @param context the current {@code ExtensionContext}; never {@code null}
159+
* @return the application context
160+
* @throws IllegalStateException if an error occurs while retrieving the
161+
* application context
162+
* @see org.springframework.test.context.TestContext#getApplicationContext()
163+
*/
164+
private ApplicationContext getApplicationContext(ExtensionContext context) {
165+
return getTestContextManager(context).getTestContext().getApplicationContext();
166+
}
167+
168+
/**
169+
* Get the {@link TestContextManager} associated with the supplied
170+
* {@code ExtensionContext}.
171+
* @return the {@code TestContextManager}; never {@code null}
172+
*/
173+
private TestContextManager getTestContextManager(ExtensionContext context) {
174+
Assert.notNull(context, "ExtensionContext must not be null");
175+
Class<?> testClass = context.getTestClass().get();
176+
Store store = context.getStore(namespace);
177+
return store.getOrComputeIfAbsent(testClass, TestContextManager::new, TestContextManager.class);
178+
}
179+
180+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.junit.jupiter;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
import org.junit.jupiter.api.extension.ExtendWith;
27+
28+
import org.springframework.context.ApplicationContextInitializer;
29+
import org.springframework.context.ConfigurableApplicationContext;
30+
import org.springframework.core.annotation.AliasFor;
31+
import org.springframework.test.context.ContextConfiguration;
32+
33+
/**
34+
* {@code @SpringJUnitConfig} is a <em>composed annotation</em> that combines
35+
* {@link ExtendWith @ExtendWith(SpringExtension.class)} from JUnit Jupiter with
36+
* {@link ContextConfiguration @ContextConfiguration} from the <em>Spring TestContext
37+
* Framework</em>.
38+
*
39+
* @author Sam Brannen
40+
* @since 5.0
41+
* @see ExtendWith
42+
* @see SpringExtension
43+
* @see ContextConfiguration
44+
* @see org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig
45+
*/
46+
@ExtendWith(SpringExtension.class)
47+
@ContextConfiguration
48+
@Documented
49+
@Inherited
50+
@Retention(RetentionPolicy.RUNTIME)
51+
@Target(ElementType.TYPE)
52+
public @interface SpringJUnitConfig {
53+
54+
/**
55+
* Alias for {@link ContextConfiguration#classes}.
56+
*/
57+
@AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
58+
Class<?>[] value() default {};
59+
60+
/**
61+
* Alias for {@link ContextConfiguration#classes}.
62+
*/
63+
@AliasFor(annotation = ContextConfiguration.class)
64+
Class<?>[] classes() default {};
65+
66+
/**
67+
* Alias for {@link ContextConfiguration#locations}.
68+
*/
69+
@AliasFor(annotation = ContextConfiguration.class)
70+
String[] locations() default {};
71+
72+
/**
73+
* Alias for {@link ContextConfiguration#initializers}.
74+
*/
75+
@AliasFor(annotation = ContextConfiguration.class)
76+
Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers() default {};
77+
78+
/**
79+
* Alias for {@link ContextConfiguration#inheritLocations}.
80+
*/
81+
@AliasFor(annotation = ContextConfiguration.class)
82+
boolean inheritLocations() default true;
83+
84+
/**
85+
* Alias for {@link ContextConfiguration#inheritInitializers}.
86+
*/
87+
@AliasFor(annotation = ContextConfiguration.class)
88+
boolean inheritInitializers() default true;
89+
90+
/**
91+
* Alias for {@link ContextConfiguration#name}.
92+
*/
93+
@AliasFor(annotation = ContextConfiguration.class)
94+
String name() default "";
95+
96+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* Core support for integrating the <em>Spring TestContext Framework</em>
3+
* with the JUnit Jupiter extension model in JUnit 5.
4+
*/
5+
package org.springframework.test.context.junit.jupiter;

0 commit comments

Comments
 (0)