Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make ApplicationContext dirty before and after test class

I have a particular class (let's say MyTest) in my Spring integration tests that is using PowerMock @PrepareForTest annotation on a Spring component: @PrepareForTest(MyComponent.class). This means that PowerMock will load this class with some modifications. The problem is, my @ContextConfiguration is defined on the superclass which is extended by MyTest, and the ApplicationContext is cached between different test classes. Now, if MyTest is run first, it will have the correct PowerMock version of MyComponent, but if not - the test will fail since the context will be loaded for another test (without @PrepareForTest).

So what I want to do is to reload my context before MyTest. I can do that via

@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)

But what if I also want to reload context after this test is done? So I will have clean MyComponent again without PowerMock modifications. Is there a way to do both BEFORE_CLASS and AFTER_CLASS?

For now I did it with the following hack:

@FixMethodOrder(MethodSorters.NAME_ASCENDING)

on MyTest and then

/**
 * Stub test to reload ApplicationContext before execution of real test methods of this class.
 */
@DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
@Test
public void aa() {
}

/**
 * Stub test to reload ApplicationContext after execution of real test methods of this class.
 */
@DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD)
@Test
public void zz() {
}

I am wondering if there is a prettier way to do that?

As a side question, is it possible to reload only certain bean and not full context?

like image 335
dty Avatar asked Sep 01 '16 16:09

dty


1 Answers

Is there a way to do both BEFORE_CLASS and AFTER_CLASS?

No, that is unfortunately not supported via @DirtiesContext.

However, what you're really saying is that you want a new ApplicationContext for MyTest that is identical to the context for the parent test class but only lives as long as MyTest. And... you don't want to affect the context cached for the parent test class.

So with that in mind, the following trick should do the job.

@RunWith(SpringJUnit4ClassRunner.class)
// Inherit config from parent and combine with local 
// static Config class to create a new context
@ContextConfiguration
@DirtiesContext
public class MyTest extends BaseTests {

    @Configuration
    static class Config {
        // No need to define any actual @Bean methods.
        // We only need to add an additional @Configuration
        // class so that we get a new ApplicationContext.
    }
}

Alternative to @DirtiesContext

If you want to have a context dirtied both before and after a test class, you can implement a custom TestExecutionListener that does exactly that. For example, the following will do the trick.

import org.springframework.core.Ordered;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;

public class DirtyContextBeforeAndAfterClassTestExecutionListener
        extends AbstractTestExecutionListener {

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
        testContext.markApplicationContextDirty(HierarchyMode.EXHAUSTIVE);
    }

    @Override
    public void afterTestClass(TestContext testContext) throws Exception {
        testContext.markApplicationContextDirty(HierarchyMode.EXHAUSTIVE);
    }

}

You can then use the custom listener in MyTest as follows.

import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.TestExecutionListeners.MergeMode;

@TestExecutionListeners(
    listeners = DirtyContextBeforeAndAfterClassTestExecutionListener.class, 
    mergeMode = MergeMode.MERGE_WITH_DEFAULTS
)
public class MyTest extends BaseTest { /* ... */ }

As a side question, is it possible to reload only certain bean and not full context?

No, that is also not possible.

Regards,

Sam (author of the Spring TestContext Framework)

like image 149
Sam Brannen Avatar answered Oct 05 '22 12:10

Sam Brannen