Grzegorz Poznachowski
Grzegorz Poznachowski

Reputation: 644

Can @ContextConfiguration in a custom annotation be merged?

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?

Upvotes: 1

Views: 2796

Answers (1)

Sam Brannen
Sam Brannen

Reputation: 31288

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)

Upvotes: 5

Related Questions