Spring profiles

Spring profiles are the method of control how the application context is built. They determine which beans and config values are used by the dependency injection container to setup the application. Using profiles allows developers and administrators to change the setup of the application with a single switch.

Integration tests

One of areas where profiles can also be particularly useful are integration tests. Integration tests, as the name suggests itself, are used to check if components of the system work together, i.e interact as expected. One of those components are databases, mail servers, billing systems or external services in general. Sometimes we don’t want to run our tests against those systems to avoid unnecessary or even harmful side effects of our tests. One other often reason is to speedup build a bit on developers’ machines but run the tests thoroughly on CI/CD environments. Beside of that it can also help to make them deterministic. Spring profiles are a convenient tool to do that.

Why integration tests need specific profile?

In one of our projects we wanted to configure context of all integration tests a bit different than the production application, i.e. we needed to change the url of the database used for those tests and, what is more important, we wanted to clean data using the liquibase. It comes to setting the following properties:

spring.datasource.url=jdbc:postgresql://localhost:5432/db-integration-tests
spring.liquibase.drop-first=true

The obvious solution was to use Spring profiles and a custom properties file since Spring reads, among others, application-{profile}.properties file where profile is the name of currently active profile. Then Spring always merges it with application.properties (the former overrides the latter). So we decided to have all Spring tests running with test profile.

Setting test profile

There are few ways to setup active profiles in Spring based test:

  • setting up IDE to activate test profile - it works and it’s flexible but requires manual config on each developer’s machine Junit Defaults

  • @TestPropertySource - it works but requires to mark all test with this annotation or to inherit from the super class having this annotation. In our case we didn’t want to introduce such inheritance and limit ourselves.

  • @ActiveProfiles - it also requires to mark all test with this annotation or to inherit from the super class having this annotation. Beside of that, it has one more major flaw: it’s final, i.e. Spring profiles passed through the environment variables are ignored since it’s priority. If you would like to active a different profile using command line or IDE you’re out of options… almost. There is one way: you can write your own ActiveProfilesResolver which will take environment variables before the values put in @ActiveProfiles annotation. You can even see the working solution here. It provides the best flexibility but with cost of maintaining an additional code.

  • application.properties in test codebase - we can simply put spring.profile.active=test in this file and all integration tests will pickup this profile but also respect the command-line override of active profiles. Sound good, right? Not really. The problem is that it shadows application.properties from main codebase (which usually stores default config values for application) so to make it useful you would have to copy and maintain both files in sync, excluding test-specific settings, which makes it even harder.

  • default profile - When spring boots up application context without explicitly set profiles it reports an interestnig thing: spring: No active profile set, falling back to profiles: default It falls back to default profile. It’s a bit misleading because when we take Environment bean and call getActiveProfiles() we get an empty array but the default profile is active though and you can use application-default.properties! So we can use it as a secondary application.properties and put test-specific settings here. In case of properties files it would probably suit our needs but when using alternative beans for tests we need to mark dummy implementations with @Profile("test"). It would look off and misleading with @Profile("default"). Also, in my personal opinion application-test.properties is the first choice to check config values for tests. That’s why we decided to activate test profile in application-default.properties:

    spring.profiles.active=test
    

    Now we can use application-test.properties as the default config for integration tests but if you decide to change the profile or add one more you’re free to do that.

Conclusion

As you can see using default profile makes possible to change the default profile for integration tests with the following benefits:

  • no need to introduce extra inheritance
  • ability to override active profiles using environment profiles
  • no extra code to maintain

Updated:

Leave a comment