Reuse spring application context across junit test classes
Asked Answered
I

5

98

We've a bunch of JUnit test cases (Integration tests) and they are logically grouped into different test classes.

We are able to load Spring application context once per test class and re-use it for all test cases in a JUnit test class as mentioned in http://static.springsource.org/spring/docs/current/spring-framework-reference/html/testing.html

However, we were just wondering if there is a way to load Spring application context only once for a bunch of JUnit test classes.

FWIW, we use Spring 3.0.5, JUnit 4.5 and use Maven to build the project.

Involute answered 14/12, 2011 at 9:17 Comment(2)
All of the answers below are great, but I don't have a context.xml. Have I annotated my way into oblivion? Any way to do this without a context.xml?Ranged
did u found the answer to your solution? i have the same problem and i want to get this done with annotations and Spring Boot.Grenoble
C
111

Yes, this is perfectly possible. All you have to do is to use the same locations attribute in your test classes:

@ContextConfiguration(locations = "classpath:test-context.xml")

Spring caches application contexts by locations attribute so if the same locations appears for the second time, Spring uses the same context rather than creating a new one.

I wrote an article about this feature: Speeding up Spring integration tests. Also it is described in details in Spring documentation: 9.3.2.1 Context management and caching.

This has an interesting implication. Because Spring does not know when JUnit is done, it caches all context forever and closes them using JVM shutdown hook. This behavior (especially when you have a lot of test classes with different locations) might lead to excessive memory usage, memory leaks, etc. Another advantage of caching context.

Crabwise answered 14/12, 2011 at 9:22 Comment(13)
Ah! Didn't realize that. We've been following this approach for a long time and I've (mistakenly) attributed the long duration for test execution to spring context loading with every test class. Will carefully check now. Thanks.Involute
I would rather say, that spring has no knowledge about the execution order of your testcases. As a result of this it can't tell if the context is required later on, or can be disposed.Grudge
I don't see how this can actually be true. Eclipse/JUnit spends 2 minutes cranking up the environment every time I do a Run As/JUnit test. This would not happen if anything were cached.Hypophosphate
Any idea if this can be done fully via annotations instead of using an XML for context definition ? I've searched a lot about it in the doc and here on SO but could not find anything which leads me to think it is not possible.Gwennie
does this not hold true if you have initializers? mine is initializing for every test in the classSlander
It is possible using annotations only. I guess it doesn't make a difference for spring if context is loaded as a xml file or by scanning classpathPieeyed
@Jean-FrançoisSavard - use classes instead of locations as @ContextConfiguration annotation propertyDowser
Is it possible to do this/combine with @SpringBootTest?Grenoble
When one of my test classes had @EnableAutoConfiguration(exclude= ..) it broke this behavior until I removed the exclude. No idea why..Thermic
Also, if you're using classes = instead of locations = the order of the classes seems to matter. Make sure it's consistent between the classes you're trying to share contexts betweenThermic
What should be the content of xml file specified in locations attribute? Can you please share a sample?Bret
The link to your Blog article is not valid anymore.Script
One important laerning, is that the context needs to be 100% identical for Spring to reuse it. If @ SpringBootTest is set with properties in one test and not the others it will get its own context. If a test uses @MockBean the context is marked as dirty, and the next test will get a new context. If the test is annotated with @ DirtiesContext it will also get its own context.Suchta
S
30

To add to Tomasz Nurkiewicz's answer, as of Spring 3.2.2 @ContextHierarchy annotation can be used to have separate, associated multiple context structure. This is helpful when multiple test classes want to share (for example) in-memory database setups (datasource, EntityManagerFactory, tx manager etc).

For example:

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("FirstTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class FirstTest {
 ...
}

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("SecondTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class SecondTest {
 ...
}

By having this setup the context that uses "test-db-setup-context.xml" will only be created once, but beans inside it can be injected to individual unit test's context

More on the manual: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#testcontext-ctx-management (search for "context hierarchy")

Seka answered 11/4, 2014 at 5:55 Comment(6)
I've multi-module maven, and I try to avoid database setup in service module (as it already loaded with tests of dataaccess module) and it doesn't work for me!Ameliorate
This worked for me! Thanks. Just to be clear, without the @ContextHierarchy annotation, spring loads my db for each test. I'm using the "classes" param: @ContextConfiguration(classes = {JpaConfigTest.class, ...Ignatzia
Any idea if this can be done fully via annotations instead of using an XML for context definition ? I've searched a lot about it in the doc and here on SO but could not find anything which leads me to think it is not possible.Gwennie
@Jean-FrançoisSavard did you have any luck in your searches (via annotationsw instead of XML)?Nappe
@Nappe I hope this is what you are looking for docs.spring.io/spring/docs/current/spring-framework-reference/…Limbert
Is this doing the same exact thing as the expected answer except splitting the contexts into 'shared' and 'separate' ?Thermic
O
7

One remarkable point is that if we use @SpringBootTests but again use @MockBean in different test classes, Spring has no way to reuse its application context for all tests.

Solution is to move all @MockBean into an common abstract class and that fix the issue.

@SpringBootTests(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
public abstract class AbstractIT {

   @MockBean
   private ProductService productService;

   @MockBean
   private InvoiceService invoiceService;

}

Then the test classes can be seen as below

public class ProductControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchProduct_ShouldSuccess() {
   }

}

public class InvoiceControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchInvoice_ShouldSuccess() {
   }

}
Ops answered 4/9, 2020 at 17:31 Comment(0)
C
6

Basically spring is smart enough to configure this for you if you have the same application context configuration across the different test classes. For instance let's say you have two classes A and B as follows:

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

In this example class A mocks bean C, whereas class B mocks bean D. So, spring considers these as two different configurations and thus would load the application context once for class A and once for class B.

If instead, we'd want to have spring share the application context between these two classes, they would have to look something as follows:

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

If you wire up your classes like this, spring would load the application context only once either for class A or B depending on which class among the two is ran first in the test suite. This could be replicated across multiple test classes, only criteria is that you should not customize the test classes differently. Any customization that results in the test class to be different from the other(in the eyes of spring) would end up creating another application context by spring.

Charyl answered 18/3, 2020 at 23:21 Comment(0)
S
0

create your configuaration class like below

@ActiveProfiles("local")
@RunWith(SpringJUnit4ClassRunner.class )
@SpringBootTest(classes ={add your spring beans configuration classess})
@TestPropertySource(properties = {"spring.config.location=classpath:application"})
@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
public class RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(S2BXISINServiceTest.class);


    //auto wire all the beans you wanted to use in your test classes
    @Autowired
    public XYZ xyz;
    @Autowired
    public ABC abc;


    }



Create your test suite like below



@RunWith(Suite.class)
@Suite.SuiteClasses({Test1.class,test2.class})
public class TestSuite extends RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(TestSuite.class);


}

Create your test classes like below

public class Test1 extends RunConfigration {


  @Test
    public void test1()
    {
    you can use autowired beans of RunConfigration classes here 
    }

}


public class Test2a extends RunConfigration {

     @Test
    public void test2()
    {
    you can use autowired beans of RunConfigration classes here 
    }


}
Starch answered 24/4, 2020 at 6:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.