diff --git a/README.md b/README.md index 062be28..fd62da6 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ -# `spring-test-junit5` +# Spring JUnit 5 Testing Support -This project serves as a proof of concept for how the _Spring TestContext Framework_ -can be fully integrated into the current [JUnit 5] snapshots using a single `Extension`. +This project serves as the official prototype for [JUnit 5][] testing support +in the [Spring TestContext Framework][] which will eventually be incorporated +into [Spring Framework][] 5.0 in conjunction with [SPR-13575][]. # Using the `SpringExtension` Currently, all that's needed to use the _Spring TestContext Framework_ with JUnit 5 -is to annotate your JUnit 5 based test class with `@ExtendWith(SpringExtension.class)` +is to annotate a JUnit 5 based test class with `@ExtendWith(SpringExtension.class)` and whatever Spring annotations you need (e.g., `@ContextConfiguration`, `@Transactional`, `@Sql`, etc.). See [`SpringExtensionTests`] for an example of this extension in action, and check out the source code of [`SpringExtension`] if you're interested in the @@ -20,17 +21,59 @@ custom annotations that are composed from Spring annotations **and** JUnit 5 annotations. Take a look at [`@SpringJUnit5Config`] for an example, and check out [`ComposedSpringExtensionTests`] for an example of `@SpringJUnit5Config` in action. +# License + +This project is released under version 2.0 of the [Apache License][]. + +# Artifacts + +There are currently no downloadable artifacts for this project. +However, if you install in a local Maven repository (see below) +the generated artifact will correspond to the following. + + - **Group ID**: `org.springframework.test` + - **Artifact ID**: `spring-test-junit5` + - **Version**: `1.0.0.BUILD-SNAPSHOT` + +# Building from Source + +This project uses a [Gradle][]-based build system. In the instructions +below, `./gradlew` is invoked from the root of the project and serves as +a cross-platform, self-contained bootstrap mechanism for the build. + +## Prerequisites and Dependencies + +- [Git][] +- [JDK 8][JDK8] update 60 or later +- [JUnit 5][] `5.0.0-SNAPSHOT` +- [Spring Framework][] `4.3.0.BUILD-SNAPSHOT` + +Be sure that your `JAVA_HOME` environment variable points to the `jdk1.8.0` folder +extracted from the JDK download. + +## Compile and Test + +Build all JARs, distribution ZIP files, and docs: + +`./gradlew build` + +## Install `spring-test-junit5` in local Maven repository + +`./gradlew install` + # Running Tests with Gradle Executing `gradlew clean test` from the command line should result in output similar to the following. ``` -Test run finished after 512 ms -[ 4 tests found ] +:junit5Test + +Test run finished after 1295 ms +[ 24 tests found ] [ 0 tests skipped ] -[ 4 tests started ] +[ 24 tests started ] [ 0 tests aborted ] -[ 4 tests successful] +[ 24 tests successful] [ 0 tests failed ] ``` @@ -38,11 +81,19 @@ Test run finished after 512 ms In order to execute the tests within an IDE, simply run [`SpringExtensionTestSuite`] as a JUnit 4 test class. +---- -[JUnit 5]: https://github.com/junit-team/junit-lambda +[Apache License]: http://www.apache.org/licenses/LICENSE-2.0 [composed annotations]: https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model#composed-annotations +[Git]: http://help.github.com/set-up-git-redirect +[Gradle]: http://gradle.org +[JDK8]: http://www.oracle.com/technetwork/java/javase/downloads +[JUnit 5]: https://github.com/junit-team/junit5 +[SPR-13575]: https://jira.spring.io/browse/SPR-13575 +[Spring Framework]: http://projects.spring.io/spring-framework/ +[Spring TestContext Framework]: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#testcontext-framework +[`@SpringJUnit5Config`]: https://github.com/sbrannen/spring-test-junit5/blob/master/src/test/java/org/springframework/test/context/junit5/SpringJUnit5Config.java [`ComposedSpringExtensionTests`]: https://github.com/sbrannen/spring-test-junit5/blob/master/src/test/java/org/springframework/test/context/junit5/ComposedSpringExtensionTests.java -[`SpringExtension`]: https://github.com/sbrannen/spring-test-junit5/blob/master/src/main/java/org/springframework/test/context/junit5/SpringExtension.java -[`SpringExtensionTests`]: https://github.com/sbrannen/spring-test-junit5/blob/master/src/test/java/org/springframework/test/context/junit5/SpringExtensionTests.java [`SpringExtensionTestSuite`]: https://github.com/sbrannen/spring-test-junit5/blob/master/src/test/java/org/springframework/test/context/junit5/SpringExtensionTestSuite.java -[`@SpringJUnit5Config`]: https://github.com/sbrannen/spring-test-junit5/blob/master/src/test/java/org/springframework/test/context/junit5/SpringJUnit5Config.java +[`SpringExtensionTests`]: https://github.com/sbrannen/spring-test-junit5/blob/master/src/test/java/org/springframework/test/context/junit5/SpringExtensionTests.java +[`SpringExtension`]: https://github.com/sbrannen/spring-test-junit5/blob/master/src/main/java/org/springframework/test/context/junit5/SpringExtension.java diff --git a/build.gradle b/build.gradle index 7e865dc..994aec9 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ buildscript { apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' +apply plugin: 'maven' apply plugin: 'org.junit.gen5.gradle' jar { @@ -17,23 +18,20 @@ jar { version = '1.0.0-SNAPSHOT' } +group = 'org.springframework.test' + ext.hamcrestVersion = '1.3' ext.jacksonVersion = '2.7.0' ext.jsonpathVersion = '2.1.0' ext.junit5Version = '5.0.0-SNAPSHOT' ext.log4JVersion = '2.5' ext.servletApiVersion = '3.1.0' -ext.springVersion = '4.2.5.RELEASE' +ext.springVersion = '4.3.0.BUILD-SNAPSHOT' repositories { mavenCentral() maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } -} - -compileTestJava { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 - options.compilerArgs += '-parameters' + maven { url 'https://repo.spring.io/libs-snapshot' } } dependencies { @@ -56,9 +54,15 @@ dependencies { testCompile("org.junit:junit4-runner:${junit5Version}") } -configurations.all { - // Do NOT cache JUnit 5 snapshot JARs for more than 60 seconds. - resolutionStrategy.cacheChangingModulesFor 60, 'seconds' +compileJava { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 +} + +compileTestJava { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + options.compilerArgs += '-parameters' } junit5 { @@ -75,6 +79,33 @@ test { exclude '**/**' } +javadoc { + options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED + options.author = true + options.header = project.name + options.addStringOption('Xdoclint:none', '-quiet') +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar) { + classifier = 'javadoc' + from javadoc +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +configurations.all { + // Do NOT cache snapshot JARs for more than 60 seconds. + resolutionStrategy.cacheChangingModulesFor 60, 'seconds' +} + task wrapper(type: Wrapper) { gradleVersion = '2.12' } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..275ee16 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +version = 1.0.0.BUILD-SNAPSHOT diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..676d4b9 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "spring-test-junit5" diff --git a/src/main/java/org/springframework/test/context/junit5/SpringBean.java b/src/main/java/org/springframework/test/context/junit5/SpringBean.java deleted file mode 100644 index 09b72fd..0000000 --- a/src/main/java/org/springframework/test/context/junit5/SpringBean.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.springframework.test.context.junit5; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.annotation.AliasFor; - -/** - * Annotation used to signal that a single method parameter should be autowired - * by Spring's dependency injection facilities. - * - *

WARNING: {@code @SpringBean} is a temporary solution - * until {@link Autowired @Autowired} is supported on parameters. - * - * @author Sam Brannen - * @since 5.0 - * @see org.springframework.beans.factory.annotation.Autowired - * @see org.springframework.beans.factory.annotation.Qualifier - * @see org.springframework.beans.factory.annotation.Value - */ -@Autowired -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface SpringBean { - - /** - * Alias for {@link Autowired#required}. - */ - @AliasFor(annotation = Autowired.class, attribute = "required") - boolean required() default true; - -} diff --git a/src/main/java/org/springframework/test/context/junit5/SpringExtension.java b/src/main/java/org/springframework/test/context/junit5/SpringExtension.java index 302dd8a..b318804 100644 --- a/src/main/java/org/springframework/test/context/junit5/SpringExtension.java +++ b/src/main/java/org/springframework/test/context/junit5/SpringExtension.java @@ -47,7 +47,6 @@ import org.springframework.test.context.TestContext; import org.springframework.test.context.TestContextManager; import org.springframework.test.context.junit5.support.MethodParameterFactory; -import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.Assert; /** @@ -59,7 +58,6 @@ * * @author Sam Brannen * @since 5.0 - * @see org.springframework.test.context.junit5.SpringBean * @see org.springframework.test.context.junit5.SpringJUnit5Config * @see org.springframework.test.context.junit5.web.SpringJUnit5WebConfig * @see org.springframework.test.context.TestContextManager @@ -131,7 +129,7 @@ public void afterEach(TestExtensionContext context) throws Exception { /** * Supports method parameter injection for parameters of type {@link ApplicationContext} - * (or a sub-type thereof) and parameters annotated with {@link SpringBean @SpringBean}, + * (or a sub-type thereof) and parameters annotated with {@link Autowired @Autowired}, * {@link Qualifier @Qualifier}, or {@link Value @Value}. * * @see #resolve @@ -152,13 +150,12 @@ public boolean supports(Parameter parameter, MethodInvocationContext methodInvoc * *

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 Qualifier @Qualifier} and {@link Value @Value} - * with support for property placeholders and SpEL expressions in {@code @Value} - * declarations. + * 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. * - *

If the parameter is annotated with {@code @Qualifier}, {@link Qualifier#value} - * will be used as the qualifier for resolving ambiguities; otherwise, the - * name of the parameter will be used as the qualifier. + *

If an explicit qualifier is not declared, the name of the parameter + * will be used as the qualifier for resolving ambiguities. * * @see #supports * @see AutowireCapableBeanFactory#resolveDependency(DependencyDescriptor, String) @@ -167,12 +164,16 @@ public boolean supports(Parameter parameter, MethodInvocationContext methodInvoc public Object resolve(Parameter parameter, MethodInvocationContext methodInvocationContext, ExtensionContext extensionContext) throws ParameterResolutionException { + Class testClass = extensionContext.getTestClass(); + boolean required = findMergedAnnotation(parameter, Autowired.class).map(Autowired::required).orElse(true); MethodParameter methodParameter = MethodParameterFactory.createSynthesizingMethodParameter(parameter); DependencyDescriptor descriptor = new DependencyDescriptor(methodParameter, required); - descriptor.setContainingClass(extensionContext.getTestClass()); - ApplicationContext applicationContext = getApplicationContext(extensionContext.getTestClass()); - return applicationContext.getAutowireCapableBeanFactory().resolveDependency(descriptor, null); + descriptor.setContainingClass(testClass); + + ApplicationContext applicationContext = getApplicationContext(testClass); + AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory(); + return beanFactory.resolveDependency(descriptor, null); } // ------------------------------------------------------------------------- @@ -180,6 +181,7 @@ public Object resolve(Parameter parameter, MethodInvocationContext methodInvocat /** * Get the {@link TestContextManager} associated with the supplied test class. * @param testClass the test class to be managed; never {@code null} + * @return the {@code TestContextManager}; never {@code null} */ private TestContextManager getTestContextManager(Class testClass) { Assert.notNull(testClass, "testClass must not be null"); @@ -190,13 +192,13 @@ private TestContextManager getTestContextManager(Class testClass) { * Get the {@link ApplicationContext} associated with the supplied test class. * @param testClass the test class whose context should be retrieved; never {@code null} * @return the application context + * @throws IllegalStateException if an error occurs while retrieving the + * application context + * @see TestContext#getApplicationContext() */ private ApplicationContext getApplicationContext(Class testClass) { Assert.notNull(testClass, "testClass must not be null"); - TestContextManager testContextManager = getTestContextManager(testClass); - // TODO Remove use of reflection once we upgrade to Spring 4.3 RC1 or higher. - TestContext testContext = (TestContext) ReflectionTestUtils.getField(testContextManager, "testContext"); - return testContext.getApplicationContext(); + return getTestContextManager(testClass).getTestContext().getApplicationContext(); } private static Optional findMergedAnnotation(AnnotatedElement element, diff --git a/src/test/java/org/springframework/test/context/junit5/SpringExtensionTests.java b/src/test/java/org/springframework/test/context/junit5/SpringExtensionTests.java index d017522..ad54a8c 100644 --- a/src/test/java/org/springframework/test/context/junit5/SpringExtensionTests.java +++ b/src/test/java/org/springframework/test/context/junit5/SpringExtensionTests.java @@ -106,13 +106,13 @@ void autowiredFields() { } @Test - void autowiredParameterByTypeForSingleBean(@SpringBean Dog dog) { + void autowiredParameterByTypeForSingleBean(@Autowired Dog dog) { assertNotNull(dog, "Dogbert should have been @Autowired by Spring"); assertEquals("Dogbert", dog.getName(), "Dog's name"); } @Test - void autowiredParameterByTypeForPrimaryBean(@SpringBean Cat primaryCat) { + void autowiredParameterByTypeForPrimaryBean(@Autowired Cat primaryCat) { assertNotNull(primaryCat, "Primary cat should have been @Autowired by Spring"); assertEquals("Catbert", primaryCat.getName(), "Primary cat's name"); } @@ -129,31 +129,31 @@ void autowiredParameterWithExplicitQualifier(@Qualifier("wally") Person person) * {@code @Qualifier("wally")}. */ @Test - void autowiredParameterWithImplicitQualifierBasedOnParameterName(@SpringBean Person wally) { + void autowiredParameterWithImplicitQualifierBasedOnParameterName(@Autowired Person wally) { assertNotNull(wally, "Wally should have been @Autowired by Spring"); assertEquals("Wally", wally.getName(), "Person's name"); } @Test - void autowiredParameterAsJavaUtilOptional(@SpringBean Optional dog) { + 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(@SpringBean Optional number) { + 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(@SpringBean(required = false) Number number) { + void autowiredParameterThatDoesNotExistButIsNotRequired(@Autowired(required = false) Number number) { assertNull(number, "Non-required number should have been @Autowired as 'null' by Spring"); } @Test - void autowiredParameterOfList(@SpringBean List peopleParam) { + 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"); } @@ -188,7 +188,7 @@ void valueParameterFromSpelExpressionWithNestedPropertyPlaceholder(@Value("#{'He } @Test - void junitAndSpringMethodInjectionCombined(@SpringBean Cat kittyCat, TestInfo testInfo, ApplicationContext context, + void junitAndSpringMethodInjectionCombined(@Autowired Cat kittyCat, TestInfo testInfo, ApplicationContext context, TestReporter testReporter) { assertNotNull(testInfo, "TestInfo should have been injected by JUnit"); diff --git a/src/test/java/org/springframework/test/context/junit5/generics/GenericComicCharactersTests.java b/src/test/java/org/springframework/test/context/junit5/generics/GenericComicCharactersTests.java index 90e446e..6dc699d 100644 --- a/src/test/java/org/springframework/test/context/junit5/generics/GenericComicCharactersTests.java +++ b/src/test/java/org/springframework/test/context/junit5/generics/GenericComicCharactersTests.java @@ -23,16 +23,16 @@ import org.junit.gen5.api.Test; import org.junit.gen5.api.extension.ExtendWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit5.SpringBean; import org.springframework.test.context.junit5.SpringExtension; import org.springframework.test.context.junit5.TestConfig; import org.springframework.test.context.junit5.comics.Character; /** * Abstract base class for integration tests that demonstrate support for - * Java generics in JUnit 5 test classes when used with the the Spring + * Java generics in JUnit 5 test classes when used with the Spring * TestContext Framework and the {@link SpringExtension}. * * @author Sam Brannen @@ -56,13 +56,13 @@ void autowiredFields() { } @Test - void autowiredParameterByTypeForSingleGenericBean(@SpringBean T character) { + void autowiredParameterByTypeForSingleGenericBean(@Autowired T character) { assertNotNull(character, "Character should have been @Autowired by Spring"); assertEquals(getExpectedName(), character.getName(), "character's name"); } - protected abstract int getExpectedNumCharacters(); + abstract int getExpectedNumCharacters(); - protected abstract String getExpectedName(); + abstract String getExpectedName(); }