atamanroman
atamanroman

Reputation: 11808

How to write junit tests against JPA which can be run in parallel?

What do I need to know to write unit tests which all access a database (through JPA+Hibernate) so that they can be run in parallel? We started using mavens parallel unit testing feature and experience test fails which don't occur when they're run in a single thread.

I'll give a short example how we're writing our tests right now. We set up the EntityManager and other classes in @Before, begin the transaction and finally insert test data which all our tests in the class need (test specific data is created in the test). @After the test is run, we roll everything back by calling Transaction.rollback(); and null all of the member variables. We run all unit tests against a in-memory hsqldb.

public class TestLogEntryDAO {
    private EntityManager em;
    private LogEntryDAO dao;

    @Before
    public void before() {
        em = Persistence.createEntityManagerFactory("junit")
            .createEntityManager();
        dao = new DAOFactory<LogEntryDAO>(LogEntryDAO.class).newInstance(em,
            Mockito.mock(ILogger.class));

        em.getTransaction().begin();

        RecordDAO recordDAO = new DAOFactory<RecordDAO>(RecordDAO.class)
            .newInstance(em, Mockito.mock(ILogger.class));
        recordDAO.setLogEntryDAO(dao);
        createTestData(); // Is this ok?
    }

    @After
    public void after() {
        em.getTransaction().rollback();
        em = null;
        dao = null;
    }

    @Test
    public void testSomething() {
        // ...
    }
}

Thanks in advance!

Upvotes: 3

Views: 2730

Answers (1)

Vineet Reynolds
Vineet Reynolds

Reputation: 76709

Caveat: I haven't attempted this. This is one of my unimplemented enhancements in my current test suite.


Parallel tests involving JPA are bound to fail if one or more of the following are true:

  • the tests running in parallel use the same set of data.
  • the tests that read data, can run at the same time as those that create, update or delete data.

If you need to run parallel tests, you could group all tests that read data into a separate suite, but this will still require serialization of tests that mutate database state. You could, if your tests permit, use separate data sets for each test that runs in parallel; note that this is not true of all applications and for all tests.

Test-specific data sets may be loaded from a file set (say, using DbUnit) or from a in-memory data set (prepared using a custom test-value factory), with each parallel test relying on a different item in the set. Quite obviously, any common data must not be mutated across tests, otherwise you will end up with transactions being rolled back (if optimistic locking is in place), or your tests will simply fail.

Also, data that can be mutated across tests must not be shared across tests. This is easily achievable if you pin different values to each test. You'll need to ensure that your tests will assert for those values or an equivalent invariant property that is specific to the test. The data-pinning can be performed using thread-ids.

To provide an example of the above, if you are running tests that will persist entity A (with attributes A1 and A2) in the database, then test T1 must persist an instance of A with values A1=x1 and A2=y1, and a parallel test T2 must persist another entity of A with values A1=x2 and A2=y2. This assumes that other tests that run in parallel will mutate value of both A1 and A2. If your tests have the characteristic of not mutating A1, then all such values of A1 can be constant across tests.

Upvotes: 1

Related Questions