Override Spring Boot Configuration Properties For Tests

Last Updated:  August 27, 2021 | Published: December 12, 2020

As a general best practice, we should externalize configuration values for our applications. This allows overriding them per stage and e.g. connect to a PayPal test instance for our development stage. Apart from overriding these properties for different stages, we can use the same technique when testing our applications to e.g. connect to a mocked external system. Spring Boot and Spring in general provide several mechanisms to override a configuration property for tests that we'll explore with this blog post.

Introduce a new property file for tests

The first approach for overriding properties helps whenever we have a set of static configuration values that are valid for multiple tests. We can create a default property file inside src/test/resources  and override common configuration values.

Let's take the following API endpoint as an example:

This controller returns the value of the configuration property welcome.message that is injected by Spring during runtime.

We can now override this property inside src/test/resources/application.properties and define a value that is used for all tests that use the default profile.

Furthermore, we can also introduce new test specific profiles, e.g. test, integration-test, web-test to group common configuration values. What's left is to activate the test profile with @ActiveProfiles:

The corresponding property filesrc/test/resources/application-test.properties can then define the value:

The same is true if we would use YAML-based property files (application.yml or application-test.yml).

We can even point to a custom property source that is e.g. not following the default Spring Boot conventions:

Use Spring Test support to override properties

Whenever we need to override a small set of Spring Boot configuration properties for a single test, introducing always a new profile is overkill. For such tests, overriding the values inline fits better.

All Spring Boot Test Slice annotations include the property attribute. This allows adding properties to the Spring Environment before running the test.

Instead of specifying the properties as part of the Spring Boot test slice annotation, we can also use @TestPropertySource here:

As mentioned, this also works for any other test slice annotation, like @SpringBootTest:

ApplicationContextInitializer to dynamically override properties

Let's assume our application communicates to several external systems on startup. A good example might be initializing a Spring WebFlux WebClient bean that fetches a valid JWT token from an authorization server on application startup.

We don't want our tests to depend on the uptime of this remote system and avoid any HTTP communication to external systems in general. An elegant solution for this is WireMock. With WireMock we can stub any HTTP response with a local webserver.

Talking to a local WireMock server for tests instead of the real authorization server requires overriding the base URL of our client. As we usually configure WireMock to use a random ephemeral port, we can't hard-code any URL for our test.

Hence we need a solution to dynamically override properties prior to starting the test application context. This is where the ApplicationContextInitializer comes into play:

Right after starting the WireMock server, we can apply a set of properties to our test application context using TestPropertyValues as we know the URL of WireMock at this time.

To then make use of this custom initializer, we have to register it for our test.

We make heavy use of this concept as a part of the Testing Spring Boot Applications Masterclass to stub several HTTP responses on application startup.

Override properties for unit tests

You might wonder: How can we override a Spring Boot property for our unit tests that don't create a Spring Test Context? That's easy!

Simply favor constructor injection, as this allows passing the value when instantiating your class under test. The following OrderService injects a Set of String values to determine the shipping costs:

Overriding the injected order.free-shipping-countries property for a unit test is now simple. We can pass the set of free shipping countries when instating the class under test using its public constructor:

No Spring Test support is needed here as this is a plain old unit test using only JUnit Jupiter.

However, if our class under test is using field injection (and we can't refactor for whatever reasons), there is a reflection-based solution available as a last resort. The Spring Test project provides a ReflectionTestUtils class that we can use to set the private field via reflection.

Let's refactor the OrderService in a bad way and use field injection:

When instantiating the BadOrderService we can only use the default constructor. Hence the freeShippingCountries field is null right after instantiating the class under test.

We can now use the ReflectionTestUtils to set the private field and inject the property:

There are some obvious drawbacks to this approach as the test will fail whenever we change the name of the field. Use this tool with caution and rather favor constructor injection.

As a summary you can use the different approaches for the following use cases:

  • create a dedicated test property file for common configuration values
  • override properties inline if they are test specific (e.g. set a feature toggle to false)
  • use an ApplicationContextInitializer for dynamic properties
  • favor constructor injection for unit testing

The source code for this blog post on how to override properties for your Spring Boot tests is available on GitHub.

PS: For a more deep-dive when it comes to testing Spring Boot applications, consider enrolling for the Testing Spring Boot Applications Masterclass.

Have fun overriding your properties,

Phil

>