diff --git a/build.gradle b/build.gradle index c20e666500..97726440a5 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,8 @@ configure(allprojects) { project -> ext.jrubyVersion = "1.7.25" // JRuby 9000 only supported through JSR-223 (StandardScriptFactory) ext.jtaVersion = "1.2" ext.junitVersion = "4.12" + ext.junitJupiterVersion = '5.0.0-SNAPSHOT' + ext.junitPlatformVersion = '1.0.0-SNAPSHOT' ext.log4jVersion = "1.2.17" ext.nettyVersion = "4.1.1.Final" ext.okhttpVersion = "2.7.5" @@ -131,6 +133,8 @@ configure(allprojects) { project -> repositories { maven { url "https://repo.spring.io/libs-release" } + // For JUnit Platform and Jupiter snapshots: + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } } dependencies { @@ -993,6 +997,7 @@ project("spring-test") { optional(project(":spring-webmvc-portlet")) optional(project(":spring-websocket")) optional("junit:junit:${junitVersion}") + optional("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}") optional("org.testng:testng:${testngVersion}") optional("javax.inject:javax.inject:1") optional("javax.servlet:javax.servlet-api:3.0.1") @@ -1020,6 +1025,8 @@ project("spring-test") { testCompile("org.hibernate:hibernate-core:${hibernate4Version}") testCompile("org.hibernate:hibernate-entitymanager:${hibernate4Version}") testCompile("org.hibernate:hibernate-validator:${hibval5Version}") + // Enable use of the JUnitPlatform Runner + testCompile("org.junit.platform:junit-platform-runner:${junitPlatformVersion}") testCompile("com.thoughtworks.xstream:xstream:${xstreamVersion}") testCompile("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}") testCompile("com.rometools:rome:${romeVersion}") @@ -1034,7 +1041,9 @@ project("spring-test") { testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") testCompile("org.apache.httpcomponents:httpclient:${httpclientVersion}") testCompile("javax.cache:cache-api:1.0.0") + testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}") testRuntime("log4j:log4j:${log4jVersion}") + testRuntime("org.ehcache:ehcache:${ehcache3Version}") testRuntime("org.terracotta:management-model:2.0.0") } @@ -1053,7 +1062,9 @@ project("spring-test") { description = 'Runs JUnit tests.' dependsOn testNG useJUnit() - exclude "**/testng/**/*.*" + scanForTestClasses = false + include(['**/*Tests.class', '**/*Test.class', '**/SpringJUnitJupiterTestSuite.class']) + exclude(['**/testng/**/*.*']) } task aggregateTestReports(type: TestReport) { diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java new file mode 100644 index 0000000000..94d1063f26 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java @@ -0,0 +1,180 @@ +/* + * Copyright 2002-2016 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 + * + * http://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.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ContainerExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.test.context.TestContextManager; +import org.springframework.test.context.junit.jupiter.support.ParameterAutowireUtils; +import org.springframework.util.Assert; + +/** + * {@code SpringExtension} integrates the Spring TestContext Framework + * into JUnit 5's Jupiter programming model. + * + *
To use this class, simply annotate a JUnit Jupiter based test class with + * {@code @ExtendWith(SpringExtension.class)}. + * + * @author Sam Brannen + * @since 5.0 + * @see org.springframework.test.context.junit.jupiter.SpringJUnitConfig + * @see org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig + * @see org.springframework.test.context.TestContextManager + */ +public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor, + BeforeEachCallback, AfterEachCallback, ParameterResolver { + + /** + * {@link Namespace} in which {@code TestContextManagers} are stored, keyed + * by test class. + */ + private static final Namespace namespace = Namespace.create(SpringExtension.class); + + /** + * Delegates to {@link TestContextManager#beforeTestClass}. + */ + @Override + public void beforeAll(ContainerExtensionContext context) throws Exception { + getTestContextManager(context).beforeTestClass(); + } + + /** + * Delegates to {@link TestContextManager#afterTestClass}. + */ + @Override + public void afterAll(ContainerExtensionContext context) throws Exception { + try { + getTestContextManager(context).afterTestClass(); + } + finally { + context.getStore(namespace).remove(context.getTestClass().get()); + } + } + + /** + * Delegates to {@link TestContextManager#prepareTestInstance}. + */ + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception { + getTestContextManager(context).prepareTestInstance(testInstance); + } + + /** + * Delegates to {@link TestContextManager#beforeTestMethod}. + */ + @Override + public void beforeEach(TestExtensionContext context) throws Exception { + Object testInstance = context.getTestInstance(); + Method testMethod = context.getTestMethod().get(); + getTestContextManager(context).beforeTestMethod(testInstance, testMethod); + } + + /** + * Delegates to {@link TestContextManager#afterTestMethod}. + */ + @Override + public void afterEach(TestExtensionContext context) throws Exception { + Object testInstance = context.getTestInstance(); + Method testMethod = context.getTestMethod().get(); + Throwable testException = context.getTestException().orElse(null); + getTestContextManager(context).afterTestMethod(testInstance, testMethod, testException); + } + + /** + * Determine if the value for the {@link Parameter} in the supplied + * {@link ParameterContext} should be autowired from the test's + * {@link ApplicationContext}. + *
Returns {@code true} if the parameter is declared in a {@link Constructor} + * that is annotated with {@link Autowired @Autowired} and otherwise delegates + * to {@link ParameterAutowireUtils#isAutowirable}. + *
WARNING: if the parameter is declared in a {@code Constructor} + * that is annotated with {@code @Autowired}, Spring will assume the responsibility + * for resolving all parameters in the constructor. Consequently, no other + * registered {@link ParameterResolver} will be able to resolve parameters. + * + * @see #resolve + * @see ParameterAutowireUtils#isAutowirable + */ + @Override + public boolean supports(ParameterContext parameterContext, ExtensionContext extensionContext) { + Parameter parameter = parameterContext.getParameter(); + Executable executable = parameter.getDeclaringExecutable(); + return (executable instanceof Constructor && AnnotatedElementUtils.hasAnnotation(executable, Autowired.class)) + || ParameterAutowireUtils.isAutowirable(parameter); + } + + /** + * Resolve a value for the {@link Parameter} in the supplied + * {@link ParameterContext} by retrieving the corresponding dependency + * from the test's {@link ApplicationContext}. + *
Delegates to {@link ParameterAutowireUtils#resolveDependency}. + * @see #supports + * @see ParameterAutowireUtils#resolveDependency + */ + @Override + public Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext) { + Parameter parameter = parameterContext.getParameter(); + Class> testClass = extensionContext.getTestClass().get(); + ApplicationContext applicationContext = getApplicationContext(extensionContext); + return ParameterAutowireUtils.resolveDependency(parameter, testClass, applicationContext); + } + + /** + * Get the {@link ApplicationContext} associated with the supplied + * {@code ExtensionContext}. + * @param context the current {@code ExtensionContext}; never {@code null} + * @return the application context + * @throws IllegalStateException if an error occurs while retrieving the + * application context + * @see org.springframework.test.context.TestContext#getApplicationContext() + */ + private ApplicationContext getApplicationContext(ExtensionContext context) { + return getTestContextManager(context).getTestContext().getApplicationContext(); + } + + /** + * Get the {@link TestContextManager} associated with the supplied + * {@code ExtensionContext}. + * @return the {@code TestContextManager}; never {@code null} + */ + private TestContextManager getTestContextManager(ExtensionContext context) { + Assert.notNull(context, "ExtensionContext must not be null"); + Class> testClass = context.getTestClass().get(); + Store store = context.getStore(namespace); + return store.getOrComputeIfAbsent(testClass, TestContextManager::new, TestContextManager.class); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringJUnitConfig.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringJUnitConfig.java new file mode 100644 index 0000000000..74a0ee98b7 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringJUnitConfig.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2016 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 + * + * http://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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.annotation.AliasFor; +import org.springframework.test.context.ContextConfiguration; + +/** + * {@code @SpringJUnitConfig} is a composed annotation that combines + * {@link ExtendWith @ExtendWith(SpringExtension.class)} from JUnit Jupiter with + * {@link ContextConfiguration @ContextConfiguration} from the Spring TestContext + * Framework. + * + * @author Sam Brannen + * @since 5.0 + * @see ExtendWith + * @see SpringExtension + * @see ContextConfiguration + * @see org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface SpringJUnitConfig { + + /** + * Alias for {@link ContextConfiguration#classes}. + */ + @AliasFor(annotation = ContextConfiguration.class, attribute = "classes") + Class>[] value() default {}; + + /** + * Alias for {@link ContextConfiguration#classes}. + */ + @AliasFor(annotation = ContextConfiguration.class) + Class>[] classes() default {}; + + /** + * Alias for {@link ContextConfiguration#locations}. + */ + @AliasFor(annotation = ContextConfiguration.class) + String[] locations() default {}; + + /** + * Alias for {@link ContextConfiguration#initializers}. + */ + @AliasFor(annotation = ContextConfiguration.class) + Class extends ApplicationContextInitializer extends ConfigurableApplicationContext>>[] initializers() default {}; + + /** + * Alias for {@link ContextConfiguration#inheritLocations}. + */ + @AliasFor(annotation = ContextConfiguration.class) + boolean inheritLocations() default true; + + /** + * Alias for {@link ContextConfiguration#inheritInitializers}. + */ + @AliasFor(annotation = ContextConfiguration.class) + boolean inheritInitializers() default true; + + /** + * Alias for {@link ContextConfiguration#name}. + */ + @AliasFor(annotation = ContextConfiguration.class) + String name() default ""; + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/package-info.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/package-info.java new file mode 100644 index 0000000000..ceba54b4f7 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/package-info.java @@ -0,0 +1,5 @@ +/** + * Core support for integrating the Spring TestContext Framework + * with the JUnit Jupiter extension model in JUnit 5. + */ +package org.springframework.test.context.junit.jupiter; diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/MethodParameterFactory.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/MethodParameterFactory.java new file mode 100644 index 0000000000..37779b44aa --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/MethodParameterFactory.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2016 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 + * + * http://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.support; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.SynthesizingMethodParameter; +import org.springframework.util.Assert; + +/** + * Factory for creating {@link MethodParameter} instances from Java 8 + * {@link Parameter Parameters}. + * + * @author Sam Brannen + * @since 5.0 + * @see ParameterAutowireUtils + * @see MethodParameter + * @see SynthesizingMethodParameter + * @see #createMethodParameter(Parameter) + * @see #createSynthesizingMethodParameter(Parameter) + */ +public abstract class MethodParameterFactory { + + private MethodParameterFactory() { + /* no-op */ + } + + /** + * Create a standard {@link MethodParameter} from the supplied {@link Parameter}. + *
Supports parameters declared in methods and constructors. + * @param parameter the parameter to create a {@code MethodParameter} for; + * never {@code null} + * @return a new {@code MethodParameter} + * @see #createSynthesizingMethodParameter(Parameter) + */ + public static MethodParameter createMethodParameter(Parameter parameter) { + Assert.notNull(parameter, "Parameter must not be null"); + Executable executable = parameter.getDeclaringExecutable(); + if (executable instanceof Method) { + return new MethodParameter((Method) executable, getIndex(parameter)); + } + // else + return new MethodParameter((Constructor>) executable, getIndex(parameter)); + } + + /** + * Create a {@link SynthesizingMethodParameter} from the supplied {@link Parameter}. + *
Supports parameters declared in methods. + * @param parameter the parameter to create a {@code SynthesizingMethodParameter} + * for; never {@code null} + * @return a new {@code SynthesizingMethodParameter} + * @throws UnsupportedOperationException if the supplied parameter is declared + * in a constructor + * @see #createMethodParameter(Parameter) + */ + public static SynthesizingMethodParameter createSynthesizingMethodParameter(Parameter parameter) { + Assert.notNull(parameter, "Parameter must not be null"); + Executable executable = parameter.getDeclaringExecutable(); + if (executable instanceof Method) { + return new SynthesizingMethodParameter((Method) executable, getIndex(parameter)); + } + // else + throw new UnsupportedOperationException( + "Cannot create a SynthesizingMethodParameter for a constructor parameter: " + parameter); + } + + private static int getIndex(Parameter parameter) { + Assert.notNull(parameter, "Parameter must not be null"); + Executable executable = parameter.getDeclaringExecutable(); + Parameter[] parameters = executable.getParameters(); + for (int i = 0; i < parameters.length; i++) { + if (parameters[i] == parameter) { + return i; + } + } + throw new IllegalStateException(String.format("Failed to resolve index of parameter [%s] in executable [%s]", + parameter, executable.toGenericString())); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/ParameterAutowireUtils.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/ParameterAutowireUtils.java new file mode 100644 index 0000000000..2c21b8fcd8 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/ParameterAutowireUtils.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2016 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 + * + * http://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.support; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Optional; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.context.ApplicationContext; +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotatedElementUtils; + +import static org.springframework.core.annotation.AnnotatedElementUtils.hasAnnotation; + +/** + * Collection of utilities related to autowiring of individual method parameters. + * + * @author Sam Brannen + * @since 5.0 + * @see MethodParameterFactory + * @see #isAutowirable(Parameter) + * @see #resolveDependency(Parameter, Class, ApplicationContext) + */ +public abstract class ParameterAutowireUtils { + + private ParameterAutowireUtils() { + /* no-op */ + } + + /** + * Determine if the supplied {@link Parameter} can potentially be + * autowired from an {@link ApplicationContext}. + *
Returns {@code true} if the supplied parameter is of type + * {@link ApplicationContext} (or a sub-type thereof) or is annotated or + * meta-annotated with {@link Autowired @Autowired}, + * {@link Qualifier @Qualifier}, or {@link Value @Value}. + * @see #resolveDependency(Parameter, Class, ApplicationContext) + */ + public static boolean isAutowirable(Parameter parameter) { + return ApplicationContext.class.isAssignableFrom(parameter.getType()) + || hasAnnotation(parameter, Autowired.class) + || hasAnnotation(parameter, Qualifier.class) + || hasAnnotation(parameter, Value.class); + } + + /** + * Resolve the dependency for the supplied {@link Parameter} from the + * supplied {@link ApplicationContext}. + *
Provides comprehensive autowiring support for individual method parameters + * on par with Spring's dependency injection facilities for autowired fields and + * methods, including support for {@link Autowired @Autowired}, + * {@link Qualifier @Qualifier}, and {@link Value @Value} with support for property + * placeholders and SpEL expressions in {@code @Value} declarations. + *
The dependency is required unless the parameter is annotated with + * {@link Autowired @Autowired} with the {@link Autowired#required required} + * flag set to {@code false}. + *
If an explicit qualifier is not declared, the name of the parameter
+ * will be used as the qualifier for resolving ambiguities.
+ * @param parameter the parameter whose dependency should be resolved
+ * @param containingClass the concrete class that contains the parameter; this may
+ * differ from the class that declares the parameter in that it may be a subclass
+ * thereof, potentially substituting type variables
+ * @param applicationContext the application context from which to resolve the
+ * dependency
+ * @return the resolved object, or {@code null} if none found
+ * @throws BeansException if dependency resolution failed
+ * @see #isAutowirable(Parameter)
+ * @see Autowired#required
+ * @see MethodParameterFactory#createSynthesizingMethodParameter(Parameter)
+ * @see AutowireCapableBeanFactory#resolveDependency(DependencyDescriptor, String)
+ */
+ public static Object resolveDependency(Parameter parameter, Class> containingClass,
+ ApplicationContext applicationContext) {
+
+ boolean required = findMergedAnnotation(parameter, Autowired.class).map(Autowired::required).orElse(true);
+ MethodParameter methodParameter = (parameter.getDeclaringExecutable() instanceof Method
+ ? MethodParameterFactory.createSynthesizingMethodParameter(parameter)
+ : MethodParameterFactory.createMethodParameter(parameter));
+ DependencyDescriptor descriptor = new DependencyDescriptor(methodParameter, required);
+ descriptor.setContainingClass(containingClass);
+
+ return applicationContext.getAutowireCapableBeanFactory().resolveDependency(descriptor, null);
+ }
+
+ private static Optional findMergedAnnotation(AnnotatedElement element,
+ Class annotationType) {
+
+ return Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(element, annotationType));
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/package-info.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/package-info.java
new file mode 100644
index 0000000000..07235151a6
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Internal support classes for integrating the Spring TestContext Framework
+ * with the JUnit Jupiter extension model in JUnit 5.
+ */
+package org.springframework.test.context.junit.jupiter.support;
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/web/SpringJUnitWebConfig.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/web/SpringJUnitWebConfig.java
new file mode 100644
index 0000000000..29b78d59ef
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/web/SpringJUnitWebConfig.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.web;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+/**
+ * {@code @SpringJUnitWebConfig} is a composed annotation that combines
+ * {@link ExtendWith @ExtendWith(SpringExtension.class)} from JUnit Jupiter with
+ * {@link ContextConfiguration @ContextConfiguration} and
+ * {@link WebAppConfiguration @WebAppConfiguration} from the Spring TestContext
+ * Framework.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ * @see ExtendWith
+ * @see SpringExtension
+ * @see ContextConfiguration
+ * @see WebAppConfiguration
+ * @see org.springframework.test.context.junit.jupiter.SpringJUnitConfig
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration
+@WebAppConfiguration
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface SpringJUnitWebConfig {
+
+ /**
+ * Alias for {@link ContextConfiguration#classes}.
+ */
+ @AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
+ Class>[] value() default {};
+
+ /**
+ * Alias for {@link ContextConfiguration#classes}.
+ */
+ @AliasFor(annotation = ContextConfiguration.class)
+ Class>[] classes() default {};
+
+ /**
+ * Alias for {@link ContextConfiguration#locations}.
+ */
+ @AliasFor(annotation = ContextConfiguration.class)
+ String[] locations() default {};
+
+ /**
+ * Alias for {@link ContextConfiguration#initializers}.
+ */
+ @AliasFor(annotation = ContextConfiguration.class)
+ Class extends ApplicationContextInitializer extends ConfigurableApplicationContext>>[] initializers() default {};
+
+ /**
+ * Alias for {@link ContextConfiguration#inheritLocations}.
+ */
+ @AliasFor(annotation = ContextConfiguration.class)
+ boolean inheritLocations() default true;
+
+ /**
+ * Alias for {@link ContextConfiguration#inheritInitializers}.
+ */
+ @AliasFor(annotation = ContextConfiguration.class)
+ boolean inheritInitializers() default true;
+
+ /**
+ * Alias for {@link ContextConfiguration#name}.
+ */
+ @AliasFor(annotation = ContextConfiguration.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link WebAppConfiguration#value}.
+ */
+ @AliasFor(annotation = WebAppConfiguration.class, attribute = "value")
+ String resourcePath() default "src/main/webapp";
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/web/package-info.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/web/package-info.java
new file mode 100644
index 0000000000..d82b9ceba8
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/web/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Web support for integrating the Spring TestContext Framework
+ * with the JUnit Jupiter extension model in JUnit 5.
+ */
+package org.springframework.test.context.junit.jupiter.web;
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/SpringJUnitJupiterTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit/SpringJUnitJupiterTestSuite.java
new file mode 100644
index 0000000000..09b7a84ee7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/SpringJUnitJupiterTestSuite.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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;
+
+import org.junit.platform.runner.IncludeEngines;
+import org.junit.platform.runner.JUnitPlatform;
+import org.junit.platform.runner.SelectPackages;
+import org.junit.runner.RunWith;
+
+/**
+ * JUnit 4 based test suite for tests that involve the Spring TestContext
+ * Framework and JUnit Jupiter (a.k.a., JUnit 5).
+ *
+ * This class intentionally does not reside in the "jupiter" package
+ * so that the entire "jupiter" package can be excluded from the Gradle
+ * build. This class is therefore responsible for executing all JUnit
+ * Jupiter based tests in Spring's official test suite.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ */
+@RunWith(JUnitPlatform.class)
+@IncludeEngines("junit-jupiter")
+@SelectPackages("org.springframework.test.context.junit.jupiter")
+public class SpringJUnitJupiterTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/ComposedSpringExtensionTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/ComposedSpringExtensionTestCase.java
new file mode 100644
index 0000000000..5644bce036
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/ComposedSpringExtensionTestCase.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.List;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite;
+import org.springframework.test.context.junit.jupiter.comics.Person;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration tests which demonstrate the composability of annotations from
+ * JUnit Jupiter and the Spring TestContext Framework.
+ *
+ * Note that {@link SpringJUnitConfig @SpringJUnitConfig} is meta-annotated
+ * with JUnit Jupiter's {@link ExtendWith @ExtendWith} and Spring's
+ * {@link ContextConfiguration @ContextConfiguration}.
+ *
+ * To run these tests in an IDE, simply run {@link SpringJUnitJupiterTestSuite}
+ * as a JUnit 4 test.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ * @see SpringExtension
+ * @see SpringJUnitConfig
+ * @see SpringExtensionTestCase
+ */
+@SpringJUnitConfig(TestConfig.class)
+@DisplayName("@SpringJUnitConfig Tests")
+class ComposedSpringExtensionTestCase {
+
+ @Autowired
+ Person dilbert;
+
+ @Autowired
+ List To run these tests in an IDE, simply run {@link SpringJUnitJupiterTestSuite}
+ * as a JUnit 4 test.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ * @see SpringExtension
+ * @see ComposedSpringExtensionTestCase
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = TestConfig.class)
+@TestPropertySource(properties = "enigma = 42")
+class SpringExtensionTestCase {
+
+ @Autowired
+ Person dilbert;
+
+ @Autowired
+ List To run these tests in an IDE, simply run {@link SpringJUnitJupiterTestSuite}
+ * as a JUnit 4 test.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ * @see SpringExtension
+ * @see SpringJUnit5ConstructorInjectionTestCase
+ */
+@SpringJUnitConfig(TestConfig.class)
+@TestPropertySource(properties = "enigma = 42")
+class SpringJUnit5AutowiredConstructorInjectionTestCase {
+
+ final ApplicationContext applicationContext;
+ final Person dilbert;
+ final Dog dog;
+ final Integer enigma;
+
+ @Autowired
+ SpringJUnit5AutowiredConstructorInjectionTestCase(ApplicationContext applicationContext, Person dilbert, Dog dog,
+ @Value("${enigma}") Integer enigma) {
+
+ this.applicationContext = applicationContext;
+ this.dilbert = dilbert;
+ this.dog = dog;
+ this.enigma = enigma;
+ }
+
+ @Test
+ void applicationContextInjected() {
+ assertNotNull(applicationContext, "ApplicationContext should have been injected by Spring");
+ assertEquals(this.dilbert, applicationContext.getBean("dilbert", Person.class));
+ }
+
+ @Test
+ void beansInjected() {
+ assertNotNull(this.dilbert, "Dilbert should have been @Autowired by Spring");
+ assertEquals("Dilbert", this.dilbert.getName(), "Person's name");
+
+ assertNotNull(this.dog, "Dogbert should have been @Autowired by Spring");
+ assertEquals("Dogbert", this.dog.getName(), "Dog's name");
+ }
+
+ @Test
+ void propertyPlaceholderInjected() {
+ assertNotNull(this.enigma, "Enigma should have been injected via @Value by Spring");
+ assertEquals(new Integer(42), this.enigma, "enigma");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnit5ConstructorInjectionTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnit5ConstructorInjectionTestCase.java
new file mode 100644
index 0000000000..6725163fe1
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnit5ConstructorInjectionTestCase.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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 org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite;
+import org.springframework.test.context.junit.jupiter.comics.Dog;
+import org.springframework.test.context.junit.jupiter.comics.Person;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration tests which demonstrate support for autowiring individual
+ * parameters in test class constructors using {@link Autowired @Autowired}
+ * and {@link Value @Value} with the Spring TestContext Framework and JUnit 5.
+ *
+ * To run these tests in an IDE, simply run {@link SpringJUnitJupiterTestSuite}
+ * as a JUnit 4 test.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ * @see SpringExtension
+ * @see SpringJUnit5AutowiredConstructorInjectionTestCase
+ */
+@SpringJUnitConfig(TestConfig.class)
+@TestPropertySource(properties = "enigma = 42")
+class SpringJUnit5ConstructorInjectionTestCase {
+
+ final ApplicationContext applicationContext;
+ final Person dilbert;
+ final Dog dog;
+ final Integer enigma;
+ final TestInfo testInfo;
+
+ SpringJUnit5ConstructorInjectionTestCase(ApplicationContext applicationContext, @Autowired Person dilbert,
+ @Autowired Dog dog, @Value("${enigma}") Integer enigma, TestInfo testInfo) {
+
+ this.applicationContext = applicationContext;
+ this.dilbert = dilbert;
+ this.dog = dog;
+ this.enigma = enigma;
+ this.testInfo = testInfo;
+ }
+
+ @Test
+ void applicationContextInjected() {
+ assertNotNull(applicationContext, "ApplicationContext should have been injected by Spring");
+ assertEquals(this.dilbert, applicationContext.getBean("dilbert", Person.class));
+ }
+
+ @Test
+ void beansInjected() {
+ assertNotNull(this.dilbert, "Dilbert should have been @Autowired by Spring");
+ assertEquals("Dilbert", this.dilbert.getName(), "Person's name");
+
+ assertNotNull(this.dog, "Dogbert should have been @Autowired by Spring");
+ assertEquals("Dogbert", this.dog.getName(), "Dog's name");
+ }
+
+ @Test
+ void propertyPlaceholderInjected() {
+ assertNotNull(this.enigma, "Enigma should have been injected via @Value by Spring");
+ assertEquals(new Integer(42), this.enigma, "enigma");
+ }
+
+ @Test
+ void testInfoInjected() {
+ assertNotNull(this.testInfo, "TestInfo should have been injected by JUnit");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConfig.java
new file mode 100644
index 0000000000..65827d5a61
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConfig.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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 org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.test.context.junit.jupiter.comics.Cat;
+import org.springframework.test.context.junit.jupiter.comics.Dog;
+import org.springframework.test.context.junit.jupiter.comics.Person;
+
+/**
+ * Demo config for tests.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ */
+@Configuration
+public class TestConfig {
+
+ @Bean
+ static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
+ return new PropertySourcesPlaceholderConfigurer();
+ }
+
+ @Bean
+ Person dilbert() {
+ return new Person("Dilbert");
+ }
+
+ @Bean
+ Person wally() {
+ return new Person("Wally");
+ }
+
+ @Bean
+ Dog dogbert() {
+ return new Dog("Dogbert");
+ }
+
+ @Primary
+ @Bean
+ Cat catbert() {
+ return new Cat("Catbert");
+ }
+
+ @Bean
+ Cat garfield() {
+ return new Cat("Garfield");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Cat.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Cat.java
new file mode 100644
index 0000000000..a66c91d16c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Cat.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.comics;
+
+/**
+ * Demo class for tests.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ */
+public class Cat extends Character {
+
+ public Cat(String name) {
+ super(name);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Character.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Character.java
new file mode 100644
index 0000000000..89a2ef9e30
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Character.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.comics;
+
+/**
+ * Demo class for tests.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ */
+public abstract class Character {
+
+ private final String name;
+
+ Character(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Dog.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Dog.java
new file mode 100644
index 0000000000..118332f919
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Dog.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.comics;
+
+/**
+ * Demo class for tests.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ */
+public class Dog extends Character {
+
+ public Dog(String name) {
+ super(name);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Person.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Person.java
new file mode 100644
index 0000000000..590f144201
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Person.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.comics;
+
+/**
+ * Demo class for tests.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ */
+public class Person extends Character {
+
+ public Person(String name) {
+ super(name);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/CatInterfaceDefaultMethodsTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/CatInterfaceDefaultMethodsTestCase.java
new file mode 100644
index 0000000000..d72660c9d4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/CatInterfaceDefaultMethodsTestCase.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.defaultmethods;
+
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.junit.jupiter.comics.Cat;
+
+/**
+ * Parameterized test class for integration tests that demonstrate support for
+ * interface default methods and Java generics in JUnit 5 test classes when used
+ * with the Spring TestContext Framework and the {@link SpringExtension}.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ */
+class CatInterfaceDefaultMethodsTestCase implements GenericComicCharactersInterfaceDefaultMethodsTestCase To run these tests in an IDE, simply run {@link SpringJUnitJupiterTestSuite}
+ * as a JUnit 4 test.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ * @see SpringExtension
+ * @see SpringJUnitWebConfig
+ * @see org.springframework.test.context.junit.jupiter.web.WebSpringExtensionTestCase
+ */
+@SpringJUnitWebConfig(WebConfig.class)
+class MultipleWebRequestsSpringExtensionTestCase {
+
+ MockMvc mockMvc;
+
+ @BeforeEach
+ void setUpMockMvc(WebApplicationContext wac) {
+ this.mockMvc = webAppContextSetup(wac)
+ .alwaysExpect(status().isOk())
+ .alwaysExpect(content().contentTypeCompatibleWith(APPLICATION_JSON))
+ .build();
+ }
+
+ @Test
+ void getPerson42() throws Exception {
+ this.mockMvc.perform(get("/person/42").accept(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.name", is("Dilbert")));
+ }
+
+ @Test
+ void getPerson99() throws Exception {
+ this.mockMvc.perform(get("/person/99").accept(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.name", is("Wally")));
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/PersonController.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/PersonController.java
new file mode 100644
index 0000000000..5863928027
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/PersonController.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.web;
+
+import org.springframework.test.context.junit.jupiter.comics.Person;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author Sam Brannen
+ * @since 5.0
+ */
+@RestController
+class PersonController {
+
+ @GetMapping("/person/{id}")
+ Person getPerson(@PathVariable long id) {
+ if (id == 42) {
+ return new Person("Dilbert");
+ }
+ return new Person("Wally");
+ }
+
+}
\ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebConfig.java
new file mode 100644
index 0000000000..b93f93036f
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebConfig.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.web;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+/**
+ * @author Sam Brannen
+ * @since 5.0
+ */
+@Configuration
+@EnableWebMvc
+class WebConfig {
+
+ @Bean
+ PersonController personController() {
+ return new PersonController();
+ }
+
+}
\ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebSpringExtensionTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebSpringExtensionTestCase.java
new file mode 100644
index 0000000000..7fe7e616e7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebSpringExtensionTestCase.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.web;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
+
+/**
+ * Integration tests which demonstrate use of the Spring MVC Test Framework and
+ * the Spring TestContext Framework with JUnit Jupiter and the
+ * {@link SpringExtension} (via a custom
+ * {@link SpringJUnitWebConfig @SpringJUnitWebConfig} composed annotation).
+ *
+ * Note how the {@link #springMvcTest(WebApplicationContext)} test method
+ * has the {@link WebApplicationContext} injected as a method parameter.
+ * This allows the {@link MockMvc} instance to be configured local to the
+ * test method without any fields in the test class.
+ *
+ * To run these tests in an IDE, simply run {@link SpringJUnitJupiterTestSuite}
+ * as a JUnit 4 test.
+ *
+ * @author Sam Brannen
+ * @since 5.0
+ * @see SpringExtension
+ * @see SpringJUnitWebConfig
+ * @see org.springframework.test.context.junit.jupiter.web.MultipleWebRequestsSpringExtensionTestCase
+ * @see org.springframework.test.context.junit.jupiter.SpringExtensionTests
+ * @see org.springframework.test.context.junit.jupiter.ComposedSpringExtensionTests
+ */
+@SpringJUnitWebConfig(WebConfig.class)
+@DisplayName("Web SpringExtension Tests")
+class WebSpringExtensionTestCase {
+
+ @Test
+ void springMvcTest(WebApplicationContext wac) throws Exception {
+ webAppContextSetup(wac).build()
+ .perform(get("/person/42").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.name", is("Dilbert")));
+ }
+
+}