Integration Testing with Spring – Profiles

Gil Zilberfeld explains Spring files and how they relate to integration tests
Standard
In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.
Dependency injectionConfigurationsNested configurationsOrganizing configurations
Primary beansAvoiding problemsProfilesMocking I - Lifecycle
Mocking II - ResetMocking III - MockBeanData I - @SQLData II - JDBC

Data III - JPA
Controllers IControllers IIConsumer tests

Today we’re going to discuss Spring profiles. Profiles are sort of a “meta configuration” in Spring. They are like a configuration for a configuration.

We usually set profiles up with either a role (“dev”, “tester”, “devops”, etc.) or with an environment (“dev”, “test”, “integration”, etc.). They are “meta” in the sense that there’s a bunch of configurations that are related to developers, and then there are a different subset for testers, for example. Those bean groups can be combined, or intersect, based on the needed configuration. We can set profiles on a configuration class or a single bean, and basically tell Spring whether to load them or not, based on the active configuration.

Let’s say we have this configuration class in production:

@Configuration
public class AppConfiguration {

	@Bean
	public Logger logger() {
		return new Logger();
	}
}

As you’ve probably guessed, it creates a real Logger. Now, in our integration test class we can use (as we saw in the @Primary post) a different configuration.

@Profile("hacker")
@Configuration
@Import(AppConfiguration.class)
public class TestConfiguration {

	@Primary
	@Bean
	public Logger faultyLogger() {
		return new FaultyLogger();
	}

  ...

}

When we use the TestConfiguration it will (normally) inject a FaultyLogger. But since we’ve put a @Profile on it, the beans in the TestConfiguration will be loaded only if the active profile when the tests run is Hacker.

The test class might look like that:

@SpringBootTest
@ContextConfiguration(classes= {HackerConfiguration.class})
@ActiveProfiles("hacker")
public class HackingTests {

    @Autowired Logger logger;

    @Test(expected = RuntimeException.class)
    public void faultyLogger_throws() {
        logger.Write("Should throw")
    }
}

The @ActiveProfiles annotation tells the integration test class under which profile it runs (obviously). So everything works fine.

If we remove the @ActiveProfiles annotation, we’ll get a NoSuchBeanDefinition exception. That is because the “hacker” profile was not activated, and therefore the TestConfiguration was not loaded. And since the test does not import the AppConfiguration directly, it doesn’t find a Logger bean.

If we would import the AppConfiguration, or the TestConfiguration but without the @ActiveProfile set to hacker, the test will fail, because it will inject the normal logger.

By now, you’re probably asking: so what?

Profile: What is it good for?

In integration testing? Not much.

Like many features in Spring, they are useful in their context. But for integration testing, we’re looking for something else.
We want tests that do not fail or pass on their context. An active profile is such a context. So we want to limit the impact of something like that on our tests.

Let’s say the application we’re testing has some hacker-context related features we want to test. It’s written using Spring’s Profile features, and so these features must be run under an active Hacker profile. That’s cool.

How would we go about testing that? We’d create a test class (or two) that use the @ActiveProfiles annotation, like we did in our example. In there we’ll put all the Hacker profile related tests. By putting all these tests together in one (or two) places, we’ve already separated them from the rest of their tests. Which helps the deterministic aspect we’re looking for.

Come to think about it, that’s how we usually separate test classes from each other, right? based on features or context?

Using Profiles doesn’t really help us in testing, it just supports Spring app code that’s written with Profile support. Which is again, cool.

There’s a minor scenario where using @ActiveProfiles with different profiles might be helpful: If the integration tests are already mashed up together without profile separation. If something goes wrong, you might want to play with @ActiveProfiles to switch profiles and debug what’s happening.

However, if you’re doing that, you might want spend more time on separating the tests into different test classes which run under different profiles.

Leave a Reply

Your email address will not be published. Required fields are marked *