Customizing the Configuration of Our Application Context

After we have finished this lesson, we

  • Know why it is a good idea to use different configuration when we run our integration tests.
  • Understand how we can use different properties files and different Spring beans in different environments.
  • Can specify the active Spring profile when we write integration tests for our application.

Why Bother?

It’s quite common that our application uses different configuration in different environments. For example, we want to use different database servers when we deploy our application to test and production environments. In other words, we want to provide environment specific configuration for the real components.

However, when we want to create a custom application context configuration that is used by our integration tests, we have a bit different goal. Typically we want to replace the real components with components that make our tests faster, less error-prone, or easier to write.

For example, we might want to:

  • Make our tests as fast as possible. We can solve this problem by moving the database configuration to a properties file and running our tests against an in-memory database.
  • Eliminate HTTP requests send to external services because we don’t want to write tests that depend on systems we cannot control. We can achieve this goal by replacing the Spring beans, which send HTTP requests to external services, with stubs.
  • Use constant time instead of the real time because we need to test code that depends from the current date and time. If we need to do this, we have to create a Spring bean that is used to access the current date and time. After we have created this bean, we can fulfill this requirement by replacing the created bean with a stub when we run our integration tests.

This lesson describes two different techniques that we can use when we want to customize the configuration of our application context.

Let’s get started.

Configuring the Available Profiles

When we want to use different configuration in different environments, the first thing that we have to do is to create a constant class that contains the names of our Spring profiles. The purpose of this class is to help us to avoid unnecessary typos and make our code easy to read, write, and maintain.

During this lesson we will use these two profiles:

  • The application profile is used when we run our application.
  • The integrationTest profile is used when we run our integration tests.

After we have added these profiles into our constant class, the source code of the Profiles class looks as follows:

1public final class Profiles {
2
3    public static final String APPLICATION = "application";
4    public static final String INTEGRATION_TEST = "integrationTest";
5
6    private Profiles() {}
7}

Let’s move on and find out how we can use different properties files when we run our application and when we run our integration tests.

Using Different Properties Files in Different Environments

It’s quite common that we externalize the configuration of our application because we want to use different configuration in different environments. The most common way to do this is to put this configuration to properties files.

Naturally, both Spring Framework and Spring Boot provides support for using different properties files in different environments. Let’s find out how we can configure the used properties files when we are using Spring Framework.

Using Spring Framework

Now we will configure our application context to use the following properties files:

  • When we run our application, we want to read the property values from the application.properties file that is found from the classpath.
  • When we run our integration tests, we want to read the property values from the integration-test.properties file that is also found from the classpath.

We can configure our application context by following these steps:

First, we have to ensure that when we run our application, the property values are read from the application.properties file. We can do this by following these steps:

  1. Add a static inner class into our application context configuration class.
  2. Annotate the static inner class with the @Configuration annotation.
  3. Annotate the static inner class with the @PropertySource annotation and configure the location of the used properties file (classpath:application.properties).
  4. Annotate the static inner class with the @Profile annotation and ensure that this configuration class is used only when the application profile is active.
 1import org.springframework.context.annotation.Bean;
 2import org.springframework.context.annotation.Configuration;
 3import org.springframework.context.annotation.Import;
 4import org.springframework.context.annotation.Profile;
 5import org.springframework.context.annotation.PropertySource;
 6
 7@Configuration
 8@Import({
 9        PersistenceContext.class,
10        WebMvcContext.class
11})
12public class ExampleApplicationContext {
13
14    @Profile(Profiles.APPLICATION)
15    @Configuration
16    @PropertySource("classpath:application.properties")
17    static class ApplicationProperties {}
18}

Second, we have to ensure that when the integrationTest profile is active, the actual property values are read from the integration-test.properties file.

After we have made the required configuration to our application context configuration class, its source code looks as follows:

 1import org.springframework.context.annotation.Bean;
 2import org.springframework.context.annotation.Configuration;
 3import org.springframework.context.annotation.Import;
 4import org.springframework.context.annotation.Profile;
 5import org.springframework.context.annotation.PropertySource;
 6
 7@Configuration
 8@Import({
 9        PersistenceContext.class,
10        WebMvcContext.class
11})
12public class ExampleApplicationContext {
13
14    @Profile(Profiles.APPLICATION)
15    @Configuration
16    @PropertySource("classpath:application.properties")
17    static class ApplicationProperties {}
18
19    @Profile(Profiles.INTEGRATION_TEST)
20    @Configuration
21    @PropertySource("classpath:integration-test.properties")
22    static class IntegrationTestProperties {}
23}

We have now configured our application to read its configuration from different properties files when we run our application and when we run our integration tests.

Next we will learn how we can achieve the same goal when we are using Spring Boot.

Using Spring Boot

If we are using Spring Boot, we don’t have to make any changes to the configuration of our application context. The reason for this is that Spring Boot provides out-of-the-box support for profile specific properties files. These properties files are loaded by following these rules:

  • The profile specific properties files are loaded from the same location as the “normal” application.properties file.
  • The file names of the profile specific properties files must use this naming convention: application-{profile}.properties.

In other words, if we use the same profiles that we used in our Spring example, our Spring Boot application uses the following properties files:

  • When we run our application, the property values are read from the application-application.properties file.
  • When we run our integration tests, the actual property values are read from the application-integrationTest.properties file.

Let’s move on and find out how we can configure our application context to use different Spring beans in different environments.

Using Different Beans in Different Environments

Our example application has a simple MessageService that simply returns the message which is given as a constructor argument. Its source code looks as follows:

 1public class MessageService {
 2
 3    private final String message;
 4
 5    public MessageService(String message) {
 6        this.message = message;
 7    }
 8
 9    public String getMessage() {
10        return message;
11    }
12}

If we want to use different MessageService beans when we run our application and when we run our integration tests, we have to make the following changes to our application context configuration class:

  1. Configure a MessageService bean and ensure that this bean is used when we run our application (the application profile is active).
  2. Configure a MessageService bean that is used when we run our integration tests (the integrationTest profile is active).

After we have configured these beans, the source code of our application context configuration class looks as follows:

 1import org.springframework.context.annotation.Bean;
 2import org.springframework.context.annotation.Configuration;
 3import org.springframework.context.annotation.Import;
 4import org.springframework.context.annotation.Profile;
 5
 6@Configuration
 7@Import({
 8        PersistenceContext.class,
 9        WebMvcContext.class
10})
11public class ExampleApplicationContext {
12
13    @Profile(Profiles.APPLICATION)
14    @Bean
15    MessageService appMessageService() {
16        return new MessageService("Hello World!");
17    }
18
19    @Profile(Profiles.INTEGRATION_TEST)
20    @Bean
21    MessageService testMessageService() {
22        return new MessageService("Hello Test!");
23    }
24}
We can use this technique if we are writing applications that use either Spring Framework or Spring Boot.

As we can see, we added the @Profile annotations directly to the methods that are annotated with the @Bean annotation. However, if we have to configure multiple different beans, it might make sense to declare these beans in different application context configuration classes and annotate these classes with the @Profile annotation.

We know how we can use different properties files and different beans in different environments. However, we have no idea how we can write integration tests that specify the active Spring profile. Let’s find out how we can do it.

Writing Integration Tests That Specify the Active Profile

When we are writing integration tests for an application that uses Spring profiles, we can specify the active Spring profile by annotating our test class with the @ActiveProfiles annotation.

After we have configured the active profile, the source code of our test class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
 4import org.junit.experimental.categories.Category;
 5import org.junit.runner.RunWith;
 6import org.springframework.test.context.ActiveProfiles;
 7import org.springframework.test.context.ContextConfiguration;
 8import org.springframework.test.context.TestExecutionListeners;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
11import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
12import org.springframework.test.context.web.ServletTestExecutionListener;
13import org.springframework.test.context.web.WebAppConfiguration;
14
15@RunWith(SpringJUnit4ClassRunner.class)
16@ContextConfiguration(classes = {IntegrationTestContext.class})
17@WebAppConfiguration
18@TestExecutionListeners({
19        DependencyInjectionTestExecutionListener.class,
20        TransactionalTestExecutionListener.class,
21        DbUnitTestExecutionListener.class,
22        ServletTestExecutionListener.class
23})
24@DatabaseSetup("messages.xml")
25@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
26@Category(IntegrationTest.class)
27@ActiveProfiles(Profiles.INTEGRATION_TEST)
28public class GetMessageTest {
29
30}

We can use the @ActiveProfiles annotation if we are writing integration tests for applications that use either Spring Framework or Spring Boot.

Additional Reading:

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us four things:

  • It is fine to replace a real component with a component that makes our tests faster, less error-prone, or easier to write.
  • We can use different properties files in different environments by annotating static inner @Configuration classes with the @Profile and @PropertySource annotations.
  • We can use different Spring beans in different environments by annotating the @Bean methods or the @Configuration classes with the @Profile annotation.
  • We can specify the active Spring profile by annotating our integration test class with the @ActiveProfiles annotation.

Get the source code from Github