13

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).

4 Answers 4

10

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)

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the feedback - very useful and insightful, especially from an authoritative source like yourself. I'd be keen to hear your views on my specific use case - to give you the full context on this: I have a bean which uses SLF4J to log various statements. I have a test which wants to confirm that the logs are written correctly (variables and format are as expected). In order to do this the test uses a JUnit OutputCaputre rule. The component uses DEBUG level. In order for my test to work reliably I must set the log level to DEBUG or above.
Continued from above: I don't want to use a logback file for the entire test suite, I just want this one test to run with the "expected" level. I have achieved this as shown above by annotating the test as a spring test and letting spring configure the context for the duration of the test based on the content of my application-test.properties file - which happens to set logging.level.com.example=DEBUG. I can see it's "unusual" to test a single component this way given I don't need the spring context at all - I'm using a side effect of having the context (namely that the log level is set)
5

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();
    }
  }
}

Comments

4

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!

4 Comments

In addition to perfect previous explanation, if you did not used constructors in your beans you can easily setting required field by using ReflectionTestUtils.setField , from org.springframework.test.util package.
Thanks for the reply, the reason I was setting up the context in my case with @SpringBootTest(properties = "spring.profiles.active=test") is that the application-test.properties class configures some aspects of the context (settings things like log levels). I originally started without this being a spring context test - it was a plain junit test which just created a new instance of the POJO - but I actually want the configuration to be applied as per the test properties.
@Dovmo I have upvoted but I didn't accept the answer yet as it does a good job or reenforcing some principals of good unit tests and gives good advice on DI best practices but doesn't really address whether or not the approach I've taken is a valid way of using thew spring annotations to control the context, rather, it instead suggests switching to a pure unit tests without a spring context and advocated constructor injection.
Updated per your comment! I should have initially stressed that your initial approach is a valid approach; I had to unit test a customer's Spring code once before, and I think setting up those specific beans via @CC is a clear and concise way to do so!
0

Sample with @ContextConfiguration:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = MyTest.TestConfig.class)
class MyTest {

    @Autowired
    MyTestBean myTestBean;

    @Test
    void apply() {
        myTestBean.apply();
    }

    @Configuration
    static class TestConfig {
        @Bean
        MyTestBean myTestBean() {
            return new MyTestBean();
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.