Go to content Go to navigation Go to search

Adapting Spring's JUnit 3.x base classes to JUnit 4

I’ve recently switched from using JUnit 3.x to JUnit 4 for most new unit tests I write. One area that causes trouble is the use of JUnit 3.x based test base classes. Spring has a tree of helper classes based on junit.framework.TestCase that make writing tests that use an ApplicationContext, dependency injection or transactions easier. These base classes can autowire your test so that your member variables are initialized and ready to go, pointing to beans managed by Spring.

I wanted the functionality provided by these base classes but wanted to continue to write tests in the JUnit4 style. The two best features of JUnit 4 are the @BeforeClass/@AfterClass annotations and the ability to have multiple setUp and tearDown methods using @Before and @After. Switching back to JUnit 3 just to use the base class seemed unfortunate.

My first attempt was to extend my JUnit 4 test from Spring’s class, AbstractDependencyInjectionSpringContextTests. When JUnit runs, it inspects your class through reflection and decides if it’s a JUnit 3 or 4 class. It sees that since Spring’s class derives from junit.framework.TestCase and so runs it as a JUnit 3 test using org.junit.internal.runners.OldTestClassRunner.

You can circumvent JUnit’s decision about this test and force it to be run as a JUnit 4 test instead using the annotation RunWith((TestClassRunner.class). This forces JUnit to use the new test runner (TestClassRunner) even though the class derives from TestCase. I created a class called SpringManagedTests that derives from AbstractDependencyInjectionSpringContextTests and is annotated with RunWith. (My tests will extend SpringManagedTests).

Next, JUnit 4 only cares about annotations and won’t run the old setUp and tearDown methods defined by the Spring classes. To get around this, add methods with Before and After annotations to call setUp and tearDown.

@Before final public void callSetup() throws Exception {
  super.setUp();
}

@After public void callTearDown() throws Exception {
  super.tearDown();
}

All features of the base class are now available, and JUnit 4 annotated test methods run as you would expect. The final test base class is below. To use it, derive your test from SpringManagedTests and read the docs on AbstractDependencyInjectionSpringContextTests for information on having Spring wire members of your class. In my case I use field injection – I have member variables declared as protected with the field name matching the bean name. When the test runs the field are populated.

This technique could be used for other JUnit 3 based test frameworks as well. It’s not as clean as having a pure JUnit 4 implementation but works well until that happens.

import org.junit.After;
import org.junit.Before;
import org.junit.internal.runners.TestClassRunner;
import org.junit.runner.RunWith;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;

/**
 * A base class for tests that load a spring application context
 * and use dependency injection to populate fields in the test class.
 * Unlike AbstractDependencyInjectionSpringContextTests, this
 * class can be used as a base class for JUnit 4 tests.
 * @author dyoung
 */
// RunWith is required to force what would otherwise look like a JUnit 3.x test
// to run with the JUnit 4 test runner.
@RunWith(TestClassRunner.class)
public class SpringManagedTests extends AbstractDependencyInjectionSpringContextTests {

    // pass through to the junit 3 calls, which are not annotated
    @Before final public void callSetup() throws Exception {
        super.setUp();
    }

    @After public void callTearDown() throws Exception {
        super.tearDown();
    }
}
  1. Thanks for an excellent solution with a very clear explanation/introduction!


    Reinier Boon    Jun 6, 09:28 AM    #
  2. Just what I was looking for – thanks very much!


    Daniel Alexiuc    Sep 5, 01:19 AM    #
  3. Better yet, use @RunWith(JUnit4ClassRunner.class) since TestClassRunner is deprecated.


    Josh Devins    Sep 6, 07:27 PM    #
  4. Hi, thanks for posting for finding a solution to this problem. Did some more investigation, looks like AbstractAnnotationAwareTransactionalTests
    is the class you are looking for


    Tony Murphy    Oct 1, 06:47 AM    #
  5. Apologies, still need
    @RunWith(JUnit4ClassRunner.class)


    Tony Murphy    Oct 1, 07:23 AM    #
  6. Thanks, this was causing me some grief.


    Elliot Smith    Oct 4, 08:52 AM    #
  7. Excellent help!


    Manoj    Oct 10, 01:13 PM    #
  8. Hey it looks i tried the same thing some how it is not loading the configurations. it is just invoking the test cases.it fails only if i combine both..
    how to make it work?

    @RunWith(TestClassRunner.class)
    public class PSListingServiceIntegrationTest extends
    AbstractDependencyInjectionSpringContextTests {

    private Advertisement advertisement = null;
    private GenericService listingService = null;

    protected String[] getConfigLocations() {
    return new String[] { “ssaeai-applicationContext-test.xml” };
    }

    @Test
    public void createListingSuccess() throws Exception {
    createListingConent();
    String status = listingService.create(advertisement);
    assertEquals(“listing creation should fail!”, “success”, status);
    }
    }


    venky    Oct 10, 07:41 PM    #
  9. venky – I believe your test is failing because you need to add the callSetup and callTearDown methods from my example. Simply annotating with TestClassRunner is not enough. setUp and tearDown aren’t called in the base class unless you call them. You should be able to copy my SpringManagedTests class, derive from it instead of AbstractDependencyInjectionSpringContextTests, and your test will work.


    Derek Young    Oct 11, 12:35 PM    #
  10. Thanks for that straight and simple solution!


    Thomas Einwaller    Oct 21, 09:52 AM    #
  11. Thanks for your great post. Actually whenever we call super.setup() from a before method, we will also call getConfigLocations() method and let all config files reload from scratch before each method. This is not the thing we need actually. We need to call super.setup() for only once. What should be do for it? Do you have any suggestions about it? Thanks a lot…


    aloe    Dec 26, 10:54 AM    #
  12. Thanks…it solved by errors :)


    Pooja    Jan 29, 02:29 PM    #
  13. Simple! clear! thanx! I’ll try that tomorrow at work!


    Jimmy    Jan 29, 05:59 PM    #
  14. Thanks Derek
    I am struggling from last few days. I am following exactly what you have described and I try to test some other code, but I am getting this error.

    java.lang.NoSuchMethodError: org.apache.commons.pool.impl.GenericObjectPool: method <init>()V not found at org.apache.commons.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1165) at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:880) at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:200) at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:350) at org.springframework.test.AbstractTransactionalSpringContextTests.startNewTransaction(AbstractTransactionalSpringContextTests.java:387) at org.springframework.test.AbstractTransactionalSpringContextTests.onSetUp(AbstractTransactionalSpringContextTests.java:217) at org.springframework.test.AbstractSingleSpringContextTests.setUp(AbstractSingleSpringContextTests.java:103) at vms.service.ShipmentServiceTest.callSetup(ShipmentServiceTest.java:26) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at org.junit.internal.runners.MethodRoadie.runBefores(MethodRoadie.java:122) at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:86) at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:77) at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:42) at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMethod(JUnit4ClassRunner.java:88) at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51) at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44) at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27) at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37) at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)

    I dont know what I am doing wrong.


    vidu    Apr 14, 09:24 PM    #
  15. vidu – this looks like a mismatch between commons-dbcp and commons-pool in your build. I would check the version of both of these in your classpath. You may have to upgrade one. As far as I know this is unrelated to the test setup.


    Derek Young    Apr 16, 10:24 AM    #
  16. As Daniel Alexiuc said, “Just what I was looking for.”


    Chris Myers    Apr 21, 01:11 AM    #
  Textile Help