1

I am working on custom Spring Boot starters. In a test starter what I wanted do to is to implement a composed annotation, which would add additional @Configuration classes to the ApplicationContext (and possibly use this annotation in a TestExecutionListener). ex:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ContextConfiguration(classes = AdditionalTestConfiguration.class)
public @interface ComposedAnnotation {
}

And use that in my Spring Boot integration test:

@RunWith(SpringJUnit4ClassRunner.class)
@WebIntegrationTest
@SpringApplicationConfiguration(Application.class)
@ComposedAnnotation
public class SomeTest {
}

No inheritance is involved. Unfortunately, it does not seem to work. I doubt it's a Spring Boot thing, rather Spring testing framework itself.

Is there any way I can achieve expected result?

  • FYI: `@ContextConfiguration(AdditionalTestConfiguration.class)` does not compile. The `value` attribute in `@ContextConfiguration` is a `String[]` of locations for XML files or Groovy scripts. – Sam Brannen Mar 02 '16 at 13:20
  • Thanks, for the catch. I was typing it from the top of my head. Corrected. – Grzegorz Poznachowski Mar 03 '16 at 12:32

1 Answers1

4

You're right: this is not an issue with Spring Boot. But it's also not an issue with spring-test.

Rather, it's the intended behavior of Spring in general. For details, check out my answer to this question: @ActiveProfiles in meta annotation and on test class not working

In summary, you cannot achieve this with two @ContextConfiguration annotations declared on an individual test class (either directly or as meta-annotations).

However, I just came up with a trick that will allow you to achieve this. Specifically, you can create an ApplicationContextInitializer (ACI) that registers one or more @Configuration classes. In your composed annotation, you can then register this ACI to register the always present @Configuration classes. And when the composed annotation is actually used, it can declare additional @Configuration classes like normal.

I just submitted a working example in this commit.

Basically, the code would look something like this:

@ContextConfiguration(loader = AnnotationConfigContextLoader.class, initializers = FooConfigInitializer.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComposedContextConfiguration {

    @AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
    Class<?>[] value() default {};
}
public class FooConfigInitializer implements ApplicationContextInitializer<GenericApplicationContext> {

    @Override
    public void initialize(GenericApplicationContext applicationContext) {
        new AnnotatedBeanDefinitionReader(applicationContext).register(FooConfig.class);
    }
}

And you can use it like this:

@RunWith(SpringRunner.class)
@ComposedContextConfiguration(BarConfig.class)
public class InitializerConfiguredViaMetaAnnotationTests { /* ... */ }

Your ApplicationContext will then be loaded from FooConfig and BarConfig.

The above examples obviously do not use Spring Boot, but the same principles should also be applicable to @SpringApplicationConfiguration.

Regards,

Sam (author of the Spring TestContext Framework)

Community
  • 1
  • 1
Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
  • Thank you for the explanation and sharing possible workaround. I don't think it will fit my needs though. What I want to achieve is to have a way to introduce mocked (*optional*) implementations of some features. i.e. I use Java 8 Clock bean for getting datetime: ZonedDateTime.now(clock). In some of my ITs i want to use FixedClock with specific value. I would like to use dedicated annotation (with parameters) to do that (setting up @Primary bean overriding the default). I'd tried programatically add primary beans in TestExecutionListeners , but I had issues with that. Any suggestions on that? – Grzegorz Poznachowski Mar 03 '16 at 16:27