3

I have the following piece of code for my abstract test class (I know XmlBeanFactory with ClassPathResource is deprecated, but it's unlikely to be the case of the problem).

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public abstract class AbstractIntegrationTest {

    /** Spring context. */
    protected static final BeanFactory context = new XmlBeanFactory(new ClassPathResource(
            "com/.../AbstractIntegrationTest-context.xml"));

    ...

}

It loads the default test configuration XML file AbstractIntegrationTest-context.xml (and then I use autowiring). I also need to use Spring in static methods annotated with @BeforeClass and @AfterClass, so I have a separate context variable pointing to the same location. But the thing is that this is a separate context, which will have different instances of beans. So how can I merge these contexts or how can I invoke Spring's bean initialization defined by @ContextConfiguration from my static context?

I have in mind a possible solution by getting rid of those static members, but I'm curious, if I can do it with relatively small changes to the code.

2 Answers 2

8

You are right, your code will produce two application contexts: one will be started, cached and maintained for you by @ContextConfiguration annotation. The second context you create yourself. It doesn't make much sense to have both.

Unfortunately JUnit is not very well suited for integration tests - mainly because you cannot have before class and after class non-static methods. I see two choices for you:

  • switch to - I know it's a big step

  • encode your setup/tear down logic in a Spring bean included in the context only during tests - but then it will run only once, before all tests.

There are also less elegant approaches. You can use static variable and inject context to it:

private static ApplicationContext context;

@AfterClass
public static afterClass() {
    //here context is accessible
}

@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
    context = applicationContext;
}

Or you can annotate your test class with @DirtiesContext and do the cleanup in some test bean:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@DirtiesContext(classMode = AFTER_CLASS)
public abstract class AbstractIntegrationTest {

    //...

}

public class OnlyForTestsBean {

    @PreDestroy
    public void willBeCalledAfterEachTestClassDuringShutdown() {
        //..
    }

}
Sign up to request clarification or add additional context in comments.

3 Comments

Concerning the second choice. Running once before all tests is not a big problem. The thing is, how to do the after class logic?
The first solutions remains the question open: "From where to get the application context to inject?" The second one must be working. I will try it.
@Vic: in the solution with setApplicationContext() setter is called by the application context loaded via @ContextConfiguration and assigns context to static variable. In principle should work.
7

Not sure whether you chose any approach here, but I encounter the same problem and solved it another way using Spring test framework's TestExecutionListener.

There are beforeTestClass and afterTestClass, so both equivalent to @BeforeClass and @AfterClass in JUnit.

The way I do it:

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners(Cleanup.class)
@ContextConfiguration(locations = { "/integrationtest/rest_test_app_ctx.xml" })
public abstract class AbstractIntegrationTest {
    // Start server for integration test.
}

You need to create a class that extends AbstractTestExecutionListener:

public class Cleanup extends AbstractTestExecutionListener
{

   @Override
   public void afterTestClass(TestContext testContext) throws Exception
   {
      System.out.println("cleaning up now");
      DomainService domainService=(DomainService)testContext.getApplicationContext().getBean("domainService");
      domainService.delete();

   }
}

By doing this, you have access to the application context and do your setup/teardown here with spring beans.

Hopefully this help anyone trying to do integration test like me using JUnit + Spring.

5 Comments

Thanks for another solution. As far as I remember, I did chose the Tomasz's approach.
Using the @TestExecutionListeners annotation appears to replace the use of the default listeners (so you lose dependency injection for example); any idea how to append to the default set instead?
@MatthewWise You can specify the merge mode of your listener: @TestExecutionListeners(value = MyListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
@OliverHernandez I must have missed that property, thanks!
Thank you thank you! thank you! Your example allowed me to solve a different-but-related problem that had me stuck for two years: how to keep a legacy code adapter with this static field in sync with the many Spring Contexts that exist during a large test-suite. See related question Spring static context accessor and integration tests, and my answer there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.