2011-12-02 36 views
7

Estoy intentando construir algunas pruebas alrededor de algunas entidades auditadas. Mi problema es que solo se envían auditorías en una transacción comprometida.Pruebas de integración con Hibernate Envers

Necesito crear/editar algunos objetos de prueba, confirmar la transacción y luego verificar las revisiones.

¿Cuál es el mejor enfoque para las pruebas de integración con envers?

Actualización: Aquí hay una clase de prueba realmente mala y no determinista de lo que quiero lograr. Preferiría hacer esto sin depender del orden de los métodos de prueba

Primero cree una cuenta y account_transaction en una sola transacción. Ambas entradas auditadas son para la revisión 1.

Segunda actualización de account_transaction en una nueva transacción. La entrada auditada está en la revisión 2.

En tercer lugar, cargue la cuenta auditada en la revisión 1 y haga algo con ella.

@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); 
    } 
+1

Compruebe [esto] (http://nurkiewicz.blogspot.com/2011/11/spring-pitfalls-transactional-tests.html) fuera - auto promoción descarada. –

+0

Gracias por la respuesta Tomasz, todavía no estoy seguro de cómo resolver mi problema de su publicación en el blog. Realmente no tengo un problema con los falsos positivos de la carga diferida, etc., más con la realización de algunas transacciones para configurar algunos datos de prueba de auditoría. Tal vez me perdí algo obvio en tu publicación. –

+1

Bueno, desplaza mi artículo a 'DbResetRule' - mi idea es evitar el uso de las pruebas JUnit' @ Transactional' y simplemente dejar que el código confirme y retrotraiga las transacciones. Obviamente, esto hace que las pruebas no sean repetibles y frágiles. Pero en lugar de revertir los cambios, propongo que se vacíe la base de datos y se restaure antes o después de cada prueba. El código está en Scala, pero esto es solo una idea general. Avíseme si esto es lo que está buscando, por lo que elaboraré un poco más en una respuesta por separado. –

Respuesta

3

De acuerdo con la sugerencia de Tomasz he usado un TransactionTemplate para lograr las confirmaciones después de cada operación de DAO. No hay una anotación @Transactional de nivel de clase.

Las entradas de auditoría de envers se insertan antes de que el método finalice, que es justo lo que necesitaba.

@ContextConfiguration("testApplicationContext.xml") 
public class TestAuditing extends AbstractJUnit4SpringContextTests { 

    @Autowired 
    private PlatformTransactionManager platformTransactionManager; 

    @Autowired 
    private PersonDao personDao; 

    private TransactionTemplate template; 

    @Before 
    public void transactionTemplate() { 
     template = new TransactionTemplate(platformTransactionManager); 
    } 

    @Test 
    public void test() { 
     Person person = createInTransaction(person("Karl", "Walsh", address("Middle of nowhere")), personDao); 
     System.out.println(person); 
    } 

    private <T> T createInTransaction(final T object, final Dao<?, T> dao) { 
     return template.execute(new TransactionCallback<T>() { 
      public T doInTransaction(TransactionStatus transactionStatus) { 
       dao.create(object); 
       return object; 
      } 
     }); 
    } 
} 
4

Soy un usuario de la ayuda de pruebas de transacciones de primavera, que se deshace pruebas cuando su hecho, y debido al diseño de envers, revisiones no se crean. Creé un truco que parece permitirle a uno "decirle" a los envers que hagan su trabajo, manualmente, antes de que la transacción se comprometa, pero permite que la primavera continúe retrocediendo.

Estos fragmentos deben ayudar. 1. Cree su propio auditlistener que anule el oyente de auditoría existente de envers. Esto permite el acceso a un miembro estático visible para pruebas unitarias. Probablemente haya una mejor manera, pero funciona.

public class AuditEventListenerForUnitTesting extends AuditEventListener { 

    public static AuditConfiguration auditConfig; 

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

modificar persistence.xml para incluir esta nueva clase de escucha en lugar de uno proporcionado por envers

(repetir para otros oyentes si es necesario)

Ahora dentro de la prueba de "unidad":

{ 
    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 
} 

Lo necesitaba porque tengo algunas consultas JDBC contra entidades de hibernación unidas a las tablas de control de versiones.

0

Las otras dos soluciones no funcionaron para mí, así que utilicé otra forma, simplemente creo una nueva transacción y forzo la confirmación. Cada vez que necesito una nueva revisión lo hago de nuevo.

@Autowired 
@Qualifier("transactionManager") 
private PlatformTransactionManager platformTransactionManager; 

@Test 
public void enversTest() throws Exception{ 
    Entity myEntity = new Entity(); 

    TransactionStatus status = platformTransactionManager.getTransaction(new DefaultTransactionDefinition()); 
    myEntity.setName("oldName"); 
    myEntity = entityDao.merge(myEntity); 
    platformTransactionManager.commit(status); // creates a revision with oldname 

    TransactionStatus newStatus = platformTransactionManager.getTransaction(new DefaultTransactionDefinition()); 
    myEntity.setName("newName"); 
    myEntity = entityDao.merge(myEntity); 
    platformTransactionManager.commit(newStatus); // creates a new revision with newName 
} 

Si utiliza @Transactional(transactionManager="transactionManager") sin embargo, podría pasar por alto las confirmaciones y considerar cada prueba como una transacción (por lo tanto no versionning múltiples veces dentro de la misma prueba ...)

2

Esto está fuertemente inspirado por this previous answer adaptado a trabajar con Envers 4.2.19.Final (JPA 2.0).Esta solución tampoco necesita la transacción para comprometerse, que era un requisito en mi caso.

En primer lugar, cree la siguiente ejecución de org.hibernate.integrator.spi.Integrator y añadirlo a la ruta de clase:

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 
    } 

} 

continuación, crear un archivo META-INF/services/org.hibernate.integrator.spi.Integrator bajo src/test/resources y pegar el nombre completo de la clase qulified integrador en ella.

En su prueba, llame al método DAO, lave la sesión de hibernación y después se hace Envers dicen proceder:

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

A continuación, puede probar el contenido de su base de datos y, finalmente, retrotraer la transacción.

Cuestiones relacionadas