Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Context Test With Just One Bean

What's the recommended way to run a spring boot test where only the one subject under test is configured in the context.

If I annotate the test with

@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.profiles.active=test")
@ContextConfiguration(classes = MyTestBean.class)

Then it seems to work - the test passes, the context starts quickly and seems to only contain the bean that I want. However, this seems like an incorrect use of the @ContextConfiguration(classes = MyTestBean.class) annotation. If I understand correctly the class that I reference is supposed to be a Configuration class, not a regular spring service bean or component for example.

Is that right? Or is this indeed a valid way to achieve this goal? I know there are more complex examples like org.springframework.boot.test.autoconfigure.json.JsonTest which use @TypeExcludeFilters(JsonExcludeFilter.class) to control the context - but this seems overkill for my use case. I just want a context with my one bean.

Clarification

I know that I can just construct the one bean I am testing as a POJO without a spring context test and remove the three annotations above. But in my precise use case I am actually reliant on some of the configuration applied to the context by settings in the application-test.properties file - which is why I've made this a Spring Boot test with a profile set. From my perspective this isn't a plain unit test of a single class in isolation of the spring context configuration - the test is reliant on certain configuration being applied (which is currently provided by the spring boot app properties). I can indeed just test the components as a POJO by creating a new instance outside of a spring context, I'm using constructor injection making the providing of necessary dependencies simple but the test does rely on things like the log level (the test actually makes assertions on certain logs being produced) which requires that the log level is set correctly (which is currently being done via logging.level.com.example=DEBUG in a properties file which sets up the spring context).

like image 991
David Avatar asked Jun 26 '18 22:06

David


3 Answers

For starters, reading the documentation first (e.g., the JavaDoc linked below in this answer) is a recommend best practice since it already answers your question.

If I understand correctly the class that I reference is supposed to be a Configuration class, not a regular spring service bean or component for example.

Is that right?

No, that's not completely correct.

Classes provided to @ContextConfiguration are typically @Configuration classes, but that is not required.

Here is an excerpt from the JavaDoc for @ContextConfiguration:

Annotated Classes

The term annotated class can refer to any of the following.

  • A class annotated with @Configuration
  • A component (i.e., a class annotated with @Component, @Service, @Repository, etc.)
  • A JSR-330 compliant class that is annotated with javax.inject annotations
  • Any other class that contains @Bean-methods

Thus you can pass any "annotated class" to @ContextConfiguration.

Or is this indeed a valid way to achieve this goal?

It is in fact a valid way to achieve that goal; however, it is also a bit unusual to load an ApplicationContext that contains a single user bean.

Regards,

Sam (author of the Spring TestContext Framework)

like image 59
Sam Brannen Avatar answered Sep 26 '22 17:09

Sam Brannen


It is definitely a reasonable and normal thing to only test a single class in a unit test.

There is no problem including just one single bean in your test context. Really, a @Configuration is (typically) just a collection of beans. You could hypothetically create a @Configuration class just with MyTestBean, but that would really be unnecessary, as you can accomplish doing the same thing listing your contextual beans with @ContextConfiguration#classes.

However, I do want to point out that for only testing a single bean in a true unit test, best practice ideally leans towards setting up the bean via the constructor and testing the class that way. This is a key reason why the Spring guys recommend using constructor vs. property injection. See the section entitled Constructor-based or setter-based DI of this article, Oliver Gierke's comment (i.e. head of Spring Data project), and google for more information. This is probably the reason you're getting a weird feeling about setting up the context for the one bean!

like image 41
Dovmo Avatar answered Sep 24 '22 17:09

Dovmo


You can also use ApplicationContextRunner to create your context using a test configuration of your choice (even with one bean if you like, but as other people have already mentioned for one bean it's more reasonable to use the constructor the classical way without using any spring magic).

What I like this way of testing is the fact that test run very fast since you don't load all the context. This method is best used when the tested bean doesn't have any Autowired dependencies otherwise it's more convenient to use @SpringBootTest.

Below is an example that illustrates the way you can use it to achieve your goal:

class MyTest {

  @Test
  void test_configuration_should_contains_my_bean() {

    new ApplicationContextRunner()
        .withUserConfiguration(TestConfiguration.class)
        .run(context -> {
          assertThat(context.getBean(MyTestBean.class)).isNotNull();
        });
  }

  @Configuraiton
  public static class TestConfiguration {
    @Bean
    public MyTestBean myTestBean(){
        new MyTestBean();
    }
  }
}
like image 27
Youssef NAIT Avatar answered Sep 22 '22 17:09

Youssef NAIT