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);
}
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);
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With