17

I need to change the Spring profiles that are active in my applicationContext within a single method of my test class, and to do so I need to run one line of code before refreshing the contest because I am using a ProfileResolver. I have tried the following:

@WebAppConfiguration
@ContextConfiguration(locations = {"/web/WEB-INF/spring.xml"})
@ActiveProfiles(resolver = BaseActiveProfilesResolverTest.class)
public class ControllerTest extends AbstractTestNGSpringContextTests {
    @Test
    public void test() throws Exception {
        codeToSetActiveProfiles(...);
        ((ConfigurableApplicationContext)this.applicationContext).refresh();
        ... tests here ...
        codeToSetActiveProfiles(... back to prior profiles ...);
        ... ideally refresh/reload the context for future tests
    }
}

But I get:

java.lang.IllegalStateException: GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once

DirtiesContext does not work for me because it is run AFTER class/method execution, not before, and I need to execute a line of code prior to running the refresh/reload anyway.

Any suggestions? I tried to have a look through the listeners/hooks that are being run, but I didn't see an obvious location to insert myself to achieve this behavior.

jmj
  • 237,923
  • 42
  • 401
  • 438
David E
  • 888
  • 2
  • 8
  • 13

3 Answers3

19

By design, programmatic refreshing of an ApplicationContext is not explicitly supported by the Spring TestContext Framework. Furthermore, it is not intended that a test method refresh a context.

Thus I would recommend that you reassess your need for a refresh and consider alternatives like placing test methods that require a different set of active profiles in a dedicated test class.

In summary, @ActiveProfiles supports declarative configuration (via value and profiles attributes) and programmatic configuration (via the resolver attribute) of the active profiles for tests, but only at the test class level (not at the method level). Another option is to implement an ApplicationContextInitializer and configure that via @ContextConfiguration(initializers=...).

The only other way to affect the ApplicationContext before it is refreshed is to implement a SmartContextLoader or extend one of the provided classes and configure it via @ContextConfiguration(loader=...). For example, AbstractGenericContextLoader.customizeContext() allows one to "customize the GenericApplicationContext created by the loader after bean definitions have been loaded into the context but before the context is refreshed."

Best regards,

Sam (author of the Spring TestContext Framework)

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
  • Do you know any exemple of a SmartContextLoader implementation (other than the provided classes) ? Because I tried to extend those classes and found that really too hard. I have stopped using @Configuration when I need other `ApplicationContext` classes than the `Generic` ones. But I also know that it is much more comfortable to use Spring test tools ... – Serge Ballesta Jul 13 '14 at 17:25
  • Thanks for your response Sam, great to have an expert in this question. I had a look through what is supplied to `AbstractGenericWebContextLoader.customizeContext`, which is a `GenericWebApplicationContext` and a `WebMergedContextConfiguration`. I didn't see any methods to set profiles, can you give me a pointer? – David E Jul 14 '14 at 05:35
  • On a `GenericWebApplicationContext` you can invoke `context.getEnvironment().setActiveProfiles(...)`, etc. – Sam Brannen Jul 14 '14 at 17:56
  • Regarding examples of `SmartContextLoader` implementations, you can Google for "implements SmartContextLoader" (including the quotes), but that won't return many results since most people either extend `AbstractContextLoader` or `AbstractGenericContextLoader`. For example, Spring Boot introduced its own `SpringApplicationContextLoader` that directly extends `AbstractContextLoader`. – Sam Brannen Jul 14 '14 at 18:06
  • Thanks Sam, I appreciate the great answers, and the great framework. We were just chatting today internally about how painful it would have been to do testing prior to it. Thanks for your hard work! – David E Jul 17 '14 at 21:09
  • You're very welcome, David. And thanks for the praise! I really appreciate it. Happy Coding :) – Sam Brannen Jul 20 '14 at 14:49
5

There's a nice little hack to trigger a context refresh - to use org.springframework.cloud.context.refresh.ContextRefresher.

I'm not 100% sure this method will suite you: it requires a spring-cloud-context dependency. However, this may be added just as a test dependency and not leak into production classpath.

To use this refresher you also need to import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration configuration, which adds a RefreshScope scope to your applicationContext which is actually doing the job under the hood.

So, modify test as follows:

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.refresh.ContextRefresher;    
// your other imports


@WebAppConfiguration
@ContextConfiguration(locations = {"/web/WEB-INF/spring.xml"}, classes = RefreshAutoConfiguration.class)
@ActiveProfiles(resolver = BaseActiveProfilesResolverTest.class)
public class ControllerTest extends AbstractTestNGSpringContextTests {

    @Autowired
    private ContextRefresher contextRefresher;

    @Test
    public void test() throws Exception {
        // doSmth before
        contextRefresher.refresh();
        // context is refreshed - continue testing
    }

}
Ivan Pronin
  • 1,768
  • 16
  • 14
1

Not all application contextes support multiple refresh. According to javadoc for AbstractRefreshableApplicationContext only subclasses of it or of AbstractRefreshableWebApplicationContext accept refresh more than once ... and GenericApplicationContext in not one of them.

You should use another class for your ApplicationContext to support hot refresh.

Edit :

As you are using @ContextConfiguration annotation, you should use a custom ContextLoader or SmartContextLoader implementation to force spring to use a less stupid ApplicationContext. But I never found a clean and neat way to to that. So when I need a XmlWebApplicationContext in my test classes, I do not use @ContextConfiguration but create and refresh my context by hand in a @Before method or at the beginning of a test.

I recognize this does not really answers your question, but you can see it as a workaround.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Agreed, although I don't see how to affect the choice of application context class that is created. By default Spring's test infrastructure is creating a GenericWebApplicationContext (subclass of GenericApplicationContext seen above in the exception). – David E Jul 13 '14 at 15:13
  • Oups, didn't notice you were using a `@ContextConfiguration` ... I've just updated my post. – Serge Ballesta Jul 13 '14 at 17:16