Context hierarchy in Spring Boot based tests

2020-06-03 07:45发布

问题:

My Spring Boot application is started like this:

new SpringApplicationBuilder()
  .sources(ParentCtxConfig.class)
  .child(ChildFirstCtxConfig.class)
  .sibling(ChildSecondCtxConfig.class)
  .run(args);

Config classes are annotated with @SpringBootApplication. As a result, I have one root context and two child web contexts.

I want to write integration test and I would like to have same context hierarchy there. I want at least to test first child context (configured with ChildFirstCtxConfig.class) with his parent context (ParentCtxConfig.class). How can I achieve that?

Currently I autowired ApplicationContext in my test so I can inspect it. I have this class annotation in the test:

@RunWith(SpringRunner.class)    
@SpringBootTest(classes = { ParentCtxConfig.class, ChildFirstCtxConfig.class }, webEnvironment = WebEnvironment.RANDOM_PORT)

but that will produce single context and I want parent-child hierarchy. I assume that I should annotate my test with @ContextHierarchy annotation.

Changing my test annotation to this seems to work exactly the same like previous example:

@RunWith(SpringRunner.class)    
@ContextConfiguration(classes = { ParentCtxConfig.class, ChildFirstCtxConfig.class })
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

But if I want to introduce @ContextHierarchy and have something like this:

@RunWith(SpringRunner.class)
@ContextHierarchy({
        @ContextConfiguration(name = "root", classes = ParentCtxConfig.class),
        @ContextConfiguration(name = "child", classes = ChildFirstCtxConfig.class)
})
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

context is not started because of bean defined in parent context can't be found/autowired in child context. Setting loader = SpringBootContextLoader.class doesn't help.

Sample code: GitHub

回答1:

Update: This issue was fixed in Spring Boot 1.5.0 as mentioned by Peter Davis.

This is a limitation of @SpringBootTest. Move accurately, it's a limitation of SpringBootContextLoader. You could work around it by using a custom context loader that configures the parent context or with a ContextCustomizer the factory for which would need to be listed in spring.factories. Here's a rough example of the latter:

src/test/resources/META-INF/spring.factories:

org.springframework.test.context.ContextCustomizerFactory=\
com.alex.demo.ctx.HierarchyContextCustomizerFactory

src/test/java/com/alex/demo/ctx/HierarchyContextCustomizerFactory.java:

package com.alex.demo.ctx;

import java.util.List;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.MergedContextConfiguration;

public class HierarchyContextCustomizerFactory implements ContextCustomizerFactory {

    @Override
    public ContextCustomizer createContextCustomizer(Class<?> testClass,
            List<ContextConfigurationAttributes> configAttributes) {
        return new ContextCustomizer() {

            @Override
            public void customizeContext(ConfigurableApplicationContext context,
                    MergedContextConfiguration mergedConfig) {
                if (mergedConfig.getParentApplicationContext() != null) {
                    context.setParent(mergedConfig.getParentApplicationContext());
                }

            }

        };
    }

}