humkins
humkins

Reputation: 10667

@Import vs @ContextConfiguration for importing beans in unit tests

I was able to set up and successfully run three different test configurations with SpringBoot 1.5.3

Method #1. Importing Bean with use of @Import annotation

@RunWith(SpringJUnit4ClassRunner.class)
@Import({MyBean.class})
public class MyBeanTest() {
    @Autowired
    private MyBean myBean;
}

Method #2. Importing Bean with use of @ContextConfiguration annotation

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MyBean.class})
public class MyBeanTest() {
    @Autowired
    private MyBean myBean;
}

Method #3 (with internal class configuration; based on the official blog post)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class MyBeanTest() {

    @Configuration
    static class ContextConfiguration {
        @Bean
        public MyBean myBean() {
            return new MyBean();
        }
    }

    @Autowired
    private MyBean myBean;

}

Taking into account @Import annotation documentation

Indicates one or more {@link Configuration @Configuration} classes to import.

and the fact that MyBean is not a configuration class, but a bean class annotated with @Component annotation it looks like Method #1 is not correct.

From @ContextConfiguration documentation

{@code @ContextConfiguration} defines class-level metadata that is used to determine how to load and configure an {@link org.springframework.context.ApplicationContext ApplicationContext} for integration tests.

Sounds like it is better applicable to unit tests, but still, should load a kind of a configuration.

Methods #1 and #2 are shorter and simpler. Method #3 looks like a correct way.

Am I right? Are there other criteria why I should use method #3, like performance or something else?

Upvotes: 18

Views: 12080

Answers (2)

Stephane Nicoll
Stephane Nicoll

Reputation: 33091

It really depends if you're using one of the testing annotations Spring Boot offers or if you're building the context from scratch. The core support in Spring Framework requires you to provide a "root configuration" via @ContextConfiguration. If you're using Spring Boot, it has its own way of detecting the root context to use. By default, the first @SpringBootConfiguration-annotated type that it finds. In a typical app structure, that's your @SpringBootApplication at the root of your application's package.

With that in mind, using @ContextConfiguration with that setup is not a great idea as it would disable that lookup and will do just more than "importing beans".

Assuming that the context has been created for you and you want to add additional beans, there are two main ways:

If you need to selectively import components (on top of the default behaviour of detecting the right context to use), then @Import is absolutely fine. In the meantime, the Javadoc of @Import was polished to mention that importing components is absolutely fine and that it isn't specific to @Configuration classes:

Indicates one or more component classes to import — typically @Configuration classes.

So, method #1 is definitely correct.

If the component is local to the test and you don't need to share it with another test, then an inner @TestConfiguration can be used. This is also documented in the reference guide.

Upvotes: 2

Pär Nilsson
Pär Nilsson

Reputation: 2349

You actually don't need to specify the loader if you go with option #3. From the doc In addition to the example from the doc you can override the env. with @TestPropertySource if you need to inject properties in the environment without using the real ones.

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from the
// static nested Config class
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
public class OrderServiceTest {

    @Configuration
    static class Config {

        // this bean will be injected into the OrderServiceTest class
        @Bean
        public OrderService orderService() {
            OrderService orderService = new OrderServiceImpl();
            // set properties, etc.
            return orderService;
        }
    }

    @Autowired
    private OrderService orderService;

    @Test
    public void testOrderService() {
        // test the orderService
    }

}

Upvotes: 3

Related Questions