Support DI of individual constructor args in @Nested tests

Prior to this commit it was impossible to have an individual
constructor argument in a @Nested (i.e., inner) test class injected via
@Autowired, @Qualifier, or @Value.

This is due to a bug in javac on JDK versions prior to 9, whereby
annotation lookups performed directly via the
java.lang.reflect.Parameter API fail for inner class constructors.

Specifically, the parameter annotations array in the compiled byte code
for the user's test class excludes an entry for the implicit enclosing
instance parameter for an inner class constructor.

This commit introduces a workaround in ParameterAutowireUtils for this
off-by-one error by transparently looking up annotations on the
preceding Parameter object (i.e., index - 1). In addition, this commit
relies on the change recently introduced in MethodParameter in order to
compensate for the same JDK bug (see SPR-16652).

Issue: SPR-16653
This commit is contained in:
Sam Brannen
2018-03-29 17:47:45 +02:00
parent 17703e5dde
commit 9244090ba0
3 changed files with 125 additions and 22 deletions

View File

@@ -18,12 +18,14 @@ package org.springframework.test.context.junit.jupiter.nested;
import org.junit.jupiter.api.Nested;
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.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite;
import org.springframework.test.context.junit.jupiter.DisabledIf;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit.jupiter.nested.NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTestCase.TopLevelConfig;
@@ -33,7 +35,7 @@ import static org.junit.jupiter.api.Assertions.*;
/**
* Integration tests that verify support for {@code @Nested} test classes in conjunction
* with the {@link SpringExtension} in a JUnit Jupiter environment ... when using
* constructor injection as opposed to field injection.
* constructor injection as opposed to field injection (see SPR-16653).
*
* <p>
* To run these tests in an IDE that does not have built-in support for the JUnit
@@ -49,8 +51,7 @@ class NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTestCase {
final String foo;
@Autowired
NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTestCase(String foo) {
NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTestCase(TestInfo testInfo, @Autowired String foo) {
this.foo = foo;
}
@@ -65,8 +66,6 @@ class NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTestCase {
final String bar;
// Only fails on JDK 8 if the parameter is annotated with @Autowired.
// Works if the constructor itself is annotated with @Autowired.
@Autowired
AutowiredConstructor(String bar) {
this.bar = bar;
@@ -81,15 +80,10 @@ class NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTestCase {
@Nested
@SpringJUnitConfig(NestedConfig.class)
@DisabledIf(expression = "#{systemProperties['java.version'].startsWith('1.8')}", //
reason = "Disabled on Java 8 due to a bug in javac in JDK 8")
// See https://github.com/junit-team/junit5/issues/1345
class AutowiredConstructorParameter {
final String bar;
// Only fails on JDK 8 if the parameter is annotated with @Autowired.
// Works if the constructor itself is annotated with @Autowired.
AutowiredConstructorParameter(@Autowired String bar) {
this.bar = bar;
}
@@ -101,6 +95,43 @@ class NestedTestsWithConstructorInjectionWithSpringAndJUnitJupiterTestCase {
}
}
@Nested
@SpringJUnitConfig(NestedConfig.class)
class QualifiedConstructorParameter {
final String bar;
QualifiedConstructorParameter(TestInfo testInfo, @Qualifier("bar") String s) {
this.bar = s;
}
@Test
void nestedTest() throws Exception {
assertEquals("foo", foo);
assertEquals("bar", bar);
}
}
@Nested
@SpringJUnitConfig(NestedConfig.class)
class SpelConstructorParameter {
final String bar;
final int answer;
SpelConstructorParameter(@Autowired String bar, TestInfo testInfo, @Value("#{ 6 * 7 }") int answer) {
this.bar = bar;
this.answer = answer;
}
@Test
void nestedTest() throws Exception {
assertEquals("foo", foo);
assertEquals("bar", bar);
assertEquals(42, answer);
}
}
// -------------------------------------------------------------------------
@Configuration