From 873fc53a2fb0612c58102c09ea9572815bf18187 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 4 Jul 2016 21:39:09 +0200 Subject: [PATCH] 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 --- build.gradle | 13 +- .../junit/jupiter/SpringExtension.java | 180 ++++++++++++++++ .../junit/jupiter/SpringJUnitConfig.java | 96 +++++++++ .../context/junit/jupiter/package-info.java | 5 + .../support/MethodParameterFactory.java | 98 +++++++++ .../support/ParameterAutowireUtils.java | 113 ++++++++++ .../junit/jupiter/support/package-info.java | 5 + .../jupiter/web/SpringJUnitWebConfig.java | 107 ++++++++++ .../junit/jupiter/web/package-info.java | 5 + .../junit/SpringJUnitJupiterTestSuite.java | 40 ++++ .../ComposedSpringExtensionTestCase.java | 75 +++++++ .../jupiter/SpringExtensionTestCase.java | 198 ++++++++++++++++++ ...AutowiredConstructorInjectionTestCase.java | 83 ++++++++ ...ingJUnit5ConstructorInjectionTestCase.java | 91 ++++++++ .../context/junit/jupiter/TestConfig.java | 67 ++++++ .../context/junit/jupiter/comics/Cat.java | 31 +++ .../junit/jupiter/comics/Character.java | 36 ++++ .../context/junit/jupiter/comics/Dog.java | 31 +++ .../context/junit/jupiter/comics/Person.java | 31 +++ .../CatInterfaceDefaultMethodsTestCase.java | 42 ++++ .../DogInterfaceDefaultMethodsTestCase.java | 42 ++++ ...actersInterfaceDefaultMethodsTestCase.java | 57 +++++ .../junit/jupiter/generics/CatTestCase.java | 42 ++++ .../junit/jupiter/generics/DogTestCase.java | 42 ++++ .../GenericComicCharactersTestCase.java | 67 ++++++ ...pleWebRequestsSpringExtensionTestCase.java | 74 +++++++ .../junit/jupiter/web/PersonController.java | 39 ++++ .../context/junit/jupiter/web/WebConfig.java | 36 ++++ .../web/WebSpringExtensionTestCase.java | 67 ++++++ 29 files changed, 1812 insertions(+), 1 deletion(-) create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringJUnitConfig.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit/jupiter/package-info.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/MethodParameterFactory.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/ParameterAutowireUtils.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit/jupiter/support/package-info.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit/jupiter/web/SpringJUnitWebConfig.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/junit/jupiter/web/package-info.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/SpringJUnitJupiterTestSuite.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/ComposedSpringExtensionTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnit5AutowiredConstructorInjectionTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnit5ConstructorInjectionTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConfig.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Cat.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Character.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Dog.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/comics/Person.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/CatInterfaceDefaultMethodsTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/DogInterfaceDefaultMethodsTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/GenericComicCharactersInterfaceDefaultMethodsTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/CatTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/DogTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericComicCharactersTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/MultipleWebRequestsSpringExtensionTestCase.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/PersonController.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebConfig.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebSpringExtensionTestCase.java 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>[] 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>[] 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 people; + + @Test + @DisplayName("ApplicationContext injected into method") + void applicationContextInjected(ApplicationContext applicationContext) { + assertNotNull(applicationContext, "ApplicationContext should have been injected into method by Spring"); + assertEquals(dilbert, applicationContext.getBean("dilbert", Person.class)); + } + + @Test + @DisplayName("Spring @Beans injected into fields") + void springBeansInjected() { + assertNotNull(dilbert, "Person should have been @Autowired by Spring"); + assertEquals("Dilbert", dilbert.getName(), "Person's name"); + assertEquals(2, people.size(), "Number of Person objects in context"); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionTestCase.java new file mode 100644 index 0000000000..2ca3bf59ca --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionTestCase.java @@ -0,0 +1,198 @@ +/* + * 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 java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; +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; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration tests which demonstrate that the Spring TestContext Framework can + * be used with JUnit Jupiter via the {@link SpringExtension}. + * + *

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 people; + + @Autowired + Dog dog; + + @Autowired + Cat cat; + + @Autowired + List cats; + + @Value("${enigma}") + Integer enigma; + + @Test + void applicationContextInjectedIntoMethod(ApplicationContext applicationContext) { + assertNotNull(applicationContext, "ApplicationContext should have been injected by Spring"); + assertEquals(this.dilbert, applicationContext.getBean("dilbert", Person.class)); + } + + @Test + void genericApplicationContextInjectedIntoMethod(GenericApplicationContext applicationContext) { + assertNotNull(applicationContext, "GenericApplicationContext should have been injected by Spring"); + assertEquals(this.dilbert, applicationContext.getBean("dilbert", Person.class)); + } + + @Test + void autowiredFields() { + assertNotNull(this.dilbert, "Dilbert should have been @Autowired by Spring"); + assertEquals("Dilbert", this.dilbert.getName(), "Person's name"); + assertEquals(2, this.people.size(), "Number of people in context"); + + assertNotNull(this.dog, "Dogbert should have been @Autowired by Spring"); + assertEquals("Dogbert", this.dog.getName(), "Dog's name"); + + assertNotNull(this.cat, "Catbert should have been @Autowired by Spring as the @Primary cat"); + assertEquals("Catbert", this.cat.getName(), "Primary cat's name"); + assertEquals(2, this.cats.size(), "Number of cats in context"); + + assertNotNull(this.enigma, "Enigma should have been injected via @Value by Spring"); + assertEquals(new Integer(42), this.enigma, "enigma"); + } + + @Test + void autowiredParameterByTypeForSingleBean(@Autowired Dog dog) { + assertNotNull(dog, "Dogbert should have been @Autowired by Spring"); + assertEquals("Dogbert", dog.getName(), "Dog's name"); + } + + @Test + void autowiredParameterByTypeForPrimaryBean(@Autowired Cat primaryCat) { + assertNotNull(primaryCat, "Primary cat should have been @Autowired by Spring"); + assertEquals("Catbert", primaryCat.getName(), "Primary cat's name"); + } + + @Test + void autowiredParameterWithExplicitQualifier(@Qualifier("wally") Person person) { + assertNotNull(person, "Wally should have been @Autowired by Spring"); + assertEquals("Wally", person.getName(), "Person's name"); + } + + /** + * NOTE: Test code must be compiled with "-g" (debug symbols) or "-parameters" in order + * for the parameter name to be used as the qualifier; otherwise, use + * {@code @Qualifier("wally")}. + */ + @Test + void autowiredParameterWithImplicitQualifierBasedOnParameterName(@Autowired Person wally) { + assertNotNull(wally, "Wally should have been @Autowired by Spring"); + assertEquals("Wally", wally.getName(), "Person's name"); + } + + @Test + void autowiredParameterAsJavaUtilOptional(@Autowired Optional dog) { + assertNotNull(dog, "Optional dog should have been @Autowired by Spring"); + assertTrue(dog.isPresent(), "Value of Optional should be 'present'"); + assertEquals("Dogbert", dog.get().getName(), "Dog's name"); + } + + @Test + void autowiredParameterThatDoesNotExistAsJavaUtilOptional(@Autowired Optional number) { + assertNotNull(number, "Optional number should have been @Autowired by Spring"); + assertFalse(number.isPresent(), "Value of Optional number should not be 'present'"); + } + + @Test + void autowiredParameterThatDoesNotExistButIsNotRequired(@Autowired(required = false) Number number) { + assertNull(number, "Non-required number should have been @Autowired as 'null' by Spring"); + } + + @Test + void autowiredParameterOfList(@Autowired List peopleParam) { + assertNotNull(peopleParam, "list of people should have been @Autowired by Spring"); + assertEquals(2, peopleParam.size(), "Number of people in context"); + } + + @Test + void valueParameterWithPrimitiveType(@Value("99") int num) { + assertEquals(99, num); + } + + @Test + void valueParameterFromPropertyPlaceholder(@Value("${enigma}") Integer enigmaParam) { + assertNotNull(enigmaParam, "Enigma should have been injected via @Value by Spring"); + assertEquals(new Integer(42), enigmaParam, "enigma"); + } + + @Test + void valueParameterFromDefaultValueForPropertyPlaceholder(@Value("${bogus:false}") Boolean defaultValue) { + assertNotNull(defaultValue, "Default value should have been injected via @Value by Spring"); + assertEquals(false, defaultValue, "default value"); + } + + @Test + void valueParameterFromSpelExpression(@Value("#{@dilbert.name}") String name) { + assertNotNull(name, "Dilbert's name should have been injected via SpEL expression in @Value by Spring"); + assertEquals("Dilbert", name, "name from SpEL expression"); + } + + @Test + void valueParameterFromSpelExpressionWithNestedPropertyPlaceholder(@Value("#{'Hello ' + ${enigma}}") String hello) { + assertNotNull(hello, "hello should have been injected via SpEL expression in @Value by Spring"); + assertEquals("Hello 42", hello, "hello from SpEL expression"); + } + + @Test + void junitAndSpringMethodInjectionCombined(@Autowired Cat kittyCat, TestInfo testInfo, ApplicationContext context, + TestReporter testReporter) { + + assertNotNull(testInfo, "TestInfo should have been injected by JUnit"); + assertNotNull(testReporter, "TestReporter should have been injected by JUnit"); + + assertNotNull(context, "ApplicationContext should have been injected by Spring"); + assertNotNull(kittyCat, "Cat should have been @Autowired by Spring"); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnit5AutowiredConstructorInjectionTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnit5AutowiredConstructorInjectionTestCase.java new file mode 100644 index 0000000000..dbc6a2f0f5 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnit5AutowiredConstructorInjectionTestCase.java @@ -0,0 +1,83 @@ +/* + * 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.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 {@link Autowired @Autowired} + * test class constructors 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 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 { + + @Override + public int getExpectedNumCharacters() { + return 2; + } + + @Override + public String getExpectedName() { + return "Catbert"; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/DogInterfaceDefaultMethodsTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/DogInterfaceDefaultMethodsTestCase.java new file mode 100644 index 0000000000..9270476589 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/DogInterfaceDefaultMethodsTestCase.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.Dog; + +/** + * 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 DogInterfaceDefaultMethodsTestCase implements GenericComicCharactersInterfaceDefaultMethodsTestCase { + + @Override + public int getExpectedNumCharacters() { + return 1; + } + + @Override + public String getExpectedName() { + return "Dogbert"; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/GenericComicCharactersInterfaceDefaultMethodsTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/GenericComicCharactersInterfaceDefaultMethodsTestCase.java new file mode 100644 index 0000000000..4bd4d511f6 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/GenericComicCharactersInterfaceDefaultMethodsTestCase.java @@ -0,0 +1,57 @@ +/* + * 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 java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit.jupiter.TestConfig; +import org.springframework.test.context.junit.jupiter.comics.Character; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Interface for integration tests that demonstrate support for interface default + * methods and Java generics in JUnit Jupiter test classes when used with the Spring + * TestContext Framework and the {@link SpringExtension}. + * + * @author Sam Brannen + * @since 5.0 + */ +@SpringJUnitConfig(TestConfig.class) +interface GenericComicCharactersInterfaceDefaultMethodsTestCase { + + @Test + default void autowiredParameterWithParameterizedList(@Autowired List characters) { + assertEquals(getExpectedNumCharacters(), characters.size(), "Number of characters in context"); + } + + @Test + default void autowiredParameterWithGenericBean(@Autowired C character) { + assertNotNull(character, "Character should have been @Autowired by Spring"); + assertEquals(getExpectedName(), character.getName(), "character's name"); + } + + int getExpectedNumCharacters(); + + String getExpectedName(); + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/CatTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/CatTestCase.java new file mode 100644 index 0000000000..ab2b6b87b0 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/CatTestCase.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.generics; + +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 + * 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 CatTestCase extends GenericComicCharactersTestCase { + + @Override + int getExpectedNumCharacters() { + return 2; + } + + @Override + String getExpectedName() { + return "Catbert"; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/DogTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/DogTestCase.java new file mode 100644 index 0000000000..a07256cb15 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/DogTestCase.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.generics; + +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.comics.Dog; + +/** + * Parameterized test class for integration tests that demonstrate support for + * 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 DogTestCase extends GenericComicCharactersTestCase { + + @Override + int getExpectedNumCharacters() { + return 1; + } + + @Override + String getExpectedName() { + return "Dogbert"; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericComicCharactersTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericComicCharactersTestCase.java new file mode 100644 index 0000000000..ce6bc23797 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericComicCharactersTestCase.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.generics; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.TestConfig; +import org.springframework.test.context.junit.jupiter.comics.Character; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Abstract base class for integration tests that demonstrate support for + * Java generics in JUnit 5 test classes when used with the Spring TestContext + * Framework and the {@link SpringExtension}. + * + * @author Sam Brannen + * @since 5.0 + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = TestConfig.class) +abstract class GenericComicCharactersTestCase { + + @Autowired + T character; + + @Autowired + List characters; + + @Test + void autowiredFields() { + assertNotNull(this.character, "Character should have been @Autowired by Spring"); + assertEquals(getExpectedName(), character.getName(), "character's name"); + assertEquals(getExpectedNumCharacters(), this.characters.size(), "Number of characters in context"); + } + + @Test + void autowiredParameterByTypeForSingleGenericBean(@Autowired T character) { + assertNotNull(character, "Character should have been @Autowired by Spring"); + assertEquals(getExpectedName(), character.getName(), "character's name"); + } + + abstract int getExpectedNumCharacters(); + + abstract String getExpectedName(); + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/MultipleWebRequestsSpringExtensionTestCase.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/MultipleWebRequestsSpringExtensionTestCase.java new file mode 100644 index 0000000000..a3d1b9fcd2 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/MultipleWebRequestsSpringExtensionTestCase.java @@ -0,0 +1,74 @@ +/* + * 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.BeforeEach; +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.http.MediaType.*; +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 how to set up a {@link MockMvc} + * instance in an {@link BeforeEach @BeforeEach} method with the + * {@link SpringExtension} (registered via a custom + * {@link SpringJUnitWebConfig @SpringJUnitWebConfig} composed annotation). + * + *

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"))); + } + +}