Spring test @ContextConfiguration and static context

12,438

Solution 1

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() {
        //..
    }

}

Solution 2

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.

Share:
12,438

Related videos on Youtube

Vic
Author by

Vic

Updated on August 02, 2022

Comments

  • Vic
    Vic over 1 year

    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.

  • Vic
    Vic over 10 years
    Thanks for another solution. As far as I remember, I did chose the Tomasz's approach.
  • Matthew Wise
    Matthew Wise over 5 years
    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?
  • Oliver Hernandez
    Oliver Hernandez over 5 years
    @MatthewWise You can specify the merge mode of your listener: @TestExecutionListeners(value = MyListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
  • Matthew Wise
    Matthew Wise over 5 years
    @OliverHernandez I must have missed that property, thanks!