Search code examples
springjunitdbunit

Can you think of a better way to only load DBbUnit once per test class with Spring?


I realise that best practise may advise on loading test data on every @Test method, however this can be painfully slow for DBUnit so I have come up with the following solution to load it only once per class:

  1. Only load a data set once per test class
  2. Support multiple data sources and those not named "dataSource" from the ApplicationContext
  3. Roll back of the inserted DBUnit data set not strictly required

While the code below works, what is bugging me is that my Test class has the static method beforeClassWithApplicationContext() but it cannot belong to an Interface because its static. Therefore my use of Reflection is being used in a non Type safe manner. Is there a more elegant solution?

/**
 * My Test class
 */
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, DbunitLoadOnceTestExecutionListener.class})
@ContextConfiguration(locations={"classpath:resources/spring/applicationContext.xml"})
public class TestClass {

    public static final String TEST_DATA_FILENAME = "Scenario-1.xml";

    public static void beforeClassWithApplicationContext(ApplicationContext ctx) throws Exception {

        DataSource ds = (DataSource)ctx.getBean("dataSourceXyz");

        IDatabaseConnection conn = new DatabaseConnection(ds.getConnection());

        IDataSet dataSet = DbUnitHelper.getDataSetFromFile(conn, TEST_DATA_FILENAME);

        InsertIdentityOperation.CLEAN_INSERT.execute(conn, dataSet);
    }

    @Test
    public void somethingToTest() {
        // do stuff...
    }
}

/**
 * My new custom TestExecutioner
 */
public class DbunitLoadOnceTestExecutionListener extends AbstractTestExecutionListener {

    final String methodName = "beforeClassWithApplicationContext";

    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {

        super.beforeTestClass(testContext);        

        Class<?> clazz = testContext.getTestClass();

        Method m = null;

        try {
            m = clazz.getDeclaredMethod(methodName, ApplicationContext.class);
        }
        catch(Exception e) {
            throw new Exception("Test class must implement " + methodName + "()", e);
        }

        m.invoke(null, testContext.getApplicationContext());
    }
}

One other thought I had was possibly creating a static singleton class for holding a reference to the ApplicationContext and populating it from DbunitLoadOnceTestExecutionListener.beforeTestClass(). I could then retrieve that singleton reference from a standard @BeforeClass method defined on TestClass. My code above calling back into each TestClass just seems a little messy.


Solution

  • After the helpful feedback from Matt and JB this is a much simpler solution to achieve the desired result

    /**
     * My Test class
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, DbunitLoadOnceTestExecutionListener.class})
    @ContextConfiguration(locations={"classpath:resources/spring/applicationContext.xml"})
    public class TestClass {
    
        private static final String TEST_DATA_FILENAME = "Scenario-1.xml";
    
        // must be static
        private static volatile boolean isDataSetLoaded = false;
    
        // use the Qualifier to select a specific dataSource
        @Autowired
        @Qualifier("dataSourceXyz")
        private DataSource dataSource;
    
        /**
         * For performance reasons, we only want to load the DBUnit data set once per test class 
         * rather than before every test method.
         * 
         * @throws Exception
         */
        @Before
        public void before() throws Exception {
    
            if(!isDataSetLoaded) {
    
                isDataSetLoaded = true;
    
                IDatabaseConnection conn = new DatabaseConnection(dataSource.getConnection());
    
                IDataSet dataSet = DbUnitHelper.getDataSetFromFile(conn, TEST_DATA_FILENAME);
    
                InsertIdentityOperation.CLEAN_INSERT.execute(conn, dataSet);
            }       
        }
    
        @Test
        public void somethingToTest() {
            // do stuff...
        }
    }
    

    The class DbunitLoadOnceTestExecutionListener is no longer requried and has been removed. It just goes to show that reading up on all the fancy techniques can sometimes cloud your own judgement :o)