Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Integration testing with Hibernate Envers

I'm trying to build some tests around some audited entities. My problem is that envers only audits on a transaction commit.

I need to create/edit some test objects, commit the transaction and then check the revisions.

What's the best approach to integration testing with envers?

Update: Here's a really bad, non-deterministic test class of what I want to achieve. I would prefer to do this without relying on the order of the test methods

First create an account and account_transaction in a single transaction. Both audited entries are for revision 1.

Second updated the account_transaction in a new transaction. The audited entry is at revision 2.

Third, load the audited account at revision 1 and do something with it.

@Transactional
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/testApplicationContext.xml"})
public class TestAuditing {

    @Autowired
    private AccountDao accountDao;

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    @Rollback(false)
    public void first() {
        Account account = account("Test Account", "xxxxxxxx", "xxxxxx");

        AccountTransaction transaction = transaction(new Date(), Deposit, 100, "Deposit");
        account.setTransactions(newArrayList(transaction));

        accountDao.create(account);
    }

    @Test
    @Rollback(false)
    public void second() {
        Account account = accountDao.getById(1L);
        AccountTransaction transaction = account.getTransactions().get(0);
        transaction.setDescription("Updated Transaction");
        accountDao.update(account);
    }

    @Test
    public void third() {
        AuditReader reader = AuditReaderFactory.get(entityManager);

        List<Number> accountRevisions = reader.getRevisions(Account.class, 1L);
        //One revision [1]

        List<Number> transactionRevisions = reader.getRevisions(AccountTransaction.class, 1L);
        //Two revisions [1, 2]

        Account currentAccount = accountDao.getById(1L);
        Account revisionAccount = (Account) reader.createQuery().forEntitiesAtRevision(Account.class, 1).getSingleResult();

        System.out.println(revisionAccount);
    }
like image 226
Karl Walsh Avatar asked Dec 02 '11 23:12

Karl Walsh


3 Answers

Updated version of @thomas-naskali's answer for Envers 5.4.15.Final

    protected void flushEnvers()
    {
        final EventSource eventSource = entityManager.unwrap(EventSource.class);
        final Session session = entityManager.unwrap(Session.class);
        final SessionImplementor sessionImplementor = entityManager.unwrap(SessionImplementor.class);

        final EnversService service = session.getSessionFactory()
            .getSessionFactoryOptions()
            .getServiceRegistry()
            .getService(EnversService.class);

        service.getAuditProcessManager().get(eventSource)
            .doBeforeTransactionCompletion(sessionImplementor);
    }
like image 128
Jan Mares Avatar answered Oct 02 '22 13:10

Jan Mares


This is strongly inspired by this previous answer adapted to work with Envers 4.2.19.Final (JPA 2.0). This solution does not need the transaction to commit either, which was a requirement in my case.

First create the following implementation of org.hibernate.integrator.spi.Integrator and add it to the classpath :

public class MyIntegrator implements Integrator {

  public static AuditConfiguration auditConfig;

  @Override
  public void integrate(Configuration configuration, SessionFactoryImplementor sessionFactory,
    SessionFactoryServiceRegistry serviceRegistry) {
    auditConfig = AuditConfiguration.getFor(configuration);
  }

  @Override
  public void integrate(MetadataImplementor metadata, SessionFactoryImplementor sessionFactory,
    SessionFactoryServiceRegistry serviceRegistry) {
    // NOP
  }

  @Override
  public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    // NOP
  }

}

then create a META-INF/services/org.hibernate.integrator.spi.Integrator file under src/test/resources and paste the fully qulified name of the integrator class into it.

In your test, call your DAO method, flush the hibernate session and after it's done tell Envers to proceed :

EventSource es = (EventSource) entityManager.getDelegate();
SessionImplementor si = entityManager.unwrap(SessionImplementor.class);
MyIntegrator.auditConfig.getSyncManager().get(es).doBeforeTransactionCompletion(si);

you can then test the content of your DB and finally rollback the transaction.

like image 38
Thomas Naskali Avatar answered Oct 02 '22 15:10

Thomas Naskali


I am a user of Spring's transaction test support which rolls back tests when their done, and due to design of envers, revisions are not created. I created a hack that appears to allow one to "tell " envers to do its work, manually, before the transaction commits, but allows spring to continue to rollback.

These snippets should help. 1. Create your own auditlistener that overrides existing envers audit listener. This allows access to a static member visible to unit tests. There is probably a better way, but it works.

public class AuditEventListenerForUnitTesting extends AuditEventListener {

   public static AuditConfiguration auditConfig;

   @Override
   public void initialize(Configuration cfg) {
      super.initialize(cfg);
      auditConfig = super.getVerCfg();
   }
}

modify your persistence.xml to include this new listener class instead of one provided by envers

(repeat for other listeners if necessary)

Now within the "unit" test:

{
   saveNewList(owner); //code that does usual entity creation
   em.flush();  
   EventSource hibSession = (EventSource) em.getDelegate();
   AuditEventListenerForUnitTesting.auditConfig.getSyncManager().get(hibSession).doBeforeTransactionCompletion(hibSession);     
   //look for envers revisions now, and they should be there
}

I needed this because I have some JDBC queries against hibernate entities joined to the versioning tables.

like image 40
jlawmi Avatar answered Oct 02 '22 14:10

jlawmi