23

In our Spring web applications, we use the Spring bean profiles to differentiate three scenarios: development, integration, and production. We use them to connect to different databases or set other constants.

Using Spring bean profiles works very well for the changing the web app environment.

The problem we have is when our integration test code needs change for the environment. In these cases, the integration test loads the application context of the web app. This way we don't have to redefine database connections, constants, etc. (applying the DRY principle).

We setup our integration tests like the following.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = ["classpath:applicationContext.xml"])
public class MyTestIT
{
   @Autowired
   @Qualifier("myRemoteURL")  // a value from the web-app's applicationContext.xml
   private String remoteURL;
   ...
 }

I can make it run locally using @ActiveProfiles, but this is hard-coded and causes our tests to fail on the build server.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = ["classpath:applicationContext.xml"])
@ActiveProfiles("development")
public class MyTestIT
{ ... }

I also tried using the @WebAppConfiguration hoping that it might somehow import the spring.profiles.active property from Maven, but that does not work.

One other note, we also need to configure our code so that developers can run the web app and then run the tests using IntelliJ's test runner (or another IDE). This is much easier for debugging integration tests.

David V
  • 11,531
  • 5
  • 42
  • 66

5 Answers5

41

As other people have already pointed out, you can opt to use Maven to set the spring.profiles.active system property, making sure not to use @ActiveProfiles, but that's not convenient for tests run within the IDE.

For a programmatic means to set the active profiles, you have a few options.

  1. Spring 3.1: write a custom ContextLoader that prepares the context by setting active profiles in the context's Environment.
  2. Spring 3.2: a custom ContextLoader remains an option, but a better choice is to implement an ApplicationContextInitializer and configure it via the initializers attribute of @ContextConfiguration. Your custom initializer can configure the Environment by programmatically setting the active profiles.
  3. Spring 4.0: the aforementioned options still exist; however, as of Spring Framework 4.0 there is a new dedicated ActiveProfilesResolver API exactly for this purpose: to programmatically determine the set of active profiles to use in a test. An ActiveProfilesResolver can be registered via the resolver attribute of @ActiveProfiles.

Regards,

Sam (author of the Spring TestContext Framework)

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
  • I'm using Spring 3.2 and item #2 works very well for it. I setup my `ApplicationContextInitializer` to call `configurableApplicationContext.getEnvironment().setDefaultProfiles("development");` which runs the development profile when I run the tests through IntelliJ. – David V Dec 13 '13 at 21:07
  • Is there an example on how to use `ActiveProfilesResolver`? I dont really know how to use it – daydreamer Jun 03 '14 at 22:19
  • 2
    Yes, there is an example of how to use a custom `ActiveProfilesResolver` in the [Testing chapter](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#testcontext-ctx-management-env-profiles-ActiveProfilesResolver) of the Spring Framework Reference Manual. – Sam Brannen Jun 05 '14 at 11:11
  • 1
    There also bug for overriding @ActiveProfiles in test classes with system property: https://jira.spring.io/browse/SPR-8982 – Grigory Kislin Oct 03 '16 at 15:00
  • @SamBrannen There is a 4th option. Write your own custom `SpringJUnit4ClassRunner`. I had to do this because Spring will load commons logging extremely eager which is terribly annoying if you want to configure logging programmatically. Of course you'll have to maintain it your own ClassRunner but at least you control the initialization and caching. – Adam Gent Apr 19 '17 at 15:22
22

I had a similar problem: I wanted to run all of my integration tests with a default profile, but allow a user to override with a profile that represented a different environment or even db flavor without having to change the @ActiveProfiles value. This is doable if you are using Spring 4.1+ with a custom ActiveProfilesResolver.

This example resolver looks for a System Property, spring.profiles.active, and if it does not exist it will delegate to the default resolver which simply uses the @ActiveProfiles annotation.

public class SystemPropertyActiveProfileResolver implements ActiveProfilesResolver {

private final DefaultActiveProfilesResolver defaultActiveProfilesResolver = new DefaultActiveProfilesResolver();

@Override
public String[] resolve(Class<?> testClass) {

    if(System.getProperties().containsKey("spring.profiles.active")) {

        final String profiles = System.getProperty("spring.profiles.active");
        return profiles.split("\\s*,\\s*");

    } else {

        return defaultActiveProfilesResolver.resolve(testClass);
    }
}

}

And in your test classes, you would use it like this:

@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles( profiles={"h2","xyz"},
resolver=SystemPropertyActiveProfileResolver.class)
public class MyTest { }

You can of course use other methods besides checking for the existence of a System Property to set the active profiles. Hope this helps somebody.

Jim Cox
  • 974
  • 7
  • 10
7

If you want to avoid hard-coding the profile you may want to use the system property spring.profiles.active and set it to whatever you need in that particular environment e.g. we have "dev", "stage" and "prod" profiles for our different environments; also we have a "test", "test-local" and "test-server" profiles for our testing.

Remember that you can have more than one profile in that system property by using a list of comma-separated values e.g. "test,test-qa".

You can specify system properties in a maven project in the maven surefire plugin or passing them like this:

mvn -DargLine="-DpropertyName=propertyValue"
Community
  • 1
  • 1
ElderMael
  • 7,000
  • 5
  • 34
  • 53
1

As @ElderMael mentioned you could use the argLine property of maven surefire plugin. Often when I need to run all the test with different specific Spring profiles I define additional maven profile. Example:

<profiles>
    <profile>
        <id>foo</id>
        <dependencies>
            <!-- additional dependencies if needed, i.e. database drivers ->
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <argLine>-Dspring.profiles.active=foo</argLine>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

With that approach you could easily run all the test with activated profile by maven command:

mvn clean test -Pfoo

The @ActiveProfile annotation is good but sometimes we need to run all the test with activated specific profiles and with hard-coded @ActiveProfile parameters it is a problem.

For example: by default integration test with H2 in-memory db, but sometimes you want to run test on the "real" database. You could define that additional maven profile and define Jenkins job. With SpringBoot you could also put additional properties to test/resources with name application-foo.yml (or properties) and those properties will be taken into account to.

Przemek Nowak
  • 7,173
  • 3
  • 53
  • 57
-1

there are many faces to this problem. in my case, a simple addition to build.gradle already helped:

test { systemProperties = System.properties }
cheesus
  • 47
  • 1
  • 6