2011-12-02 19 views
7

Sto provando a costruire alcuni test intorno ad alcune entità controllate. Il mio problema è che envers controlla solo su un commit di transazione.Test di integrazione con Hibernate Envers

Devo creare/modificare alcuni oggetti di test, eseguire il commit della transazione e quindi controllare le revisioni.

Qual è l'approccio migliore ai test di integrazione con envers?

Aggiornamento: Ecco una classe di test davvero non valida e non deterministica di ciò che voglio ottenere. Preferirei farlo senza fare affidamento sull'ordine dei metodi di test

Per prima cosa creare un account e account_transaction in una singola transazione. Entrambe le voci controllate sono per la revisione 1.

In secondo luogo l'account_transaction in una nuova transazione. La voce controllata è alla revisione 2.

In terzo luogo, caricare l'account controllato alla revisione 1 e fare qualcosa con esso.

@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

Controllare [questo] (http://nurkiewicz.blogspot.com/2011/11/spring-pitfalls-transactional-tests.html) out - auto-promozione spudorata. –

+0

Grazie per la risposta Tomasz, non sono ancora sicuro di come risolvere il mio problema dal tuo post sul blog però. In realtà non ho un problema con i falsi positivi del caricamento lazy, ecc., In realtà con alcune transazioni per l'impostazione di alcuni dati di test di controllo. Forse mi sono perso qualcosa di ovvio nel tuo post? –

+1

Bene, scorri il mio articolo in 'DbResetRule' - la mia idea è di evitare l'uso dei test JUnit di' @ Transactional' e di lasciare solo il tuo codice di commit e di rollback delle transazioni. Ovviamente questo rende i test non ripetibili e fragili. Ma invece di ripristinare le modifiche, propongo di scaricare il database e ripristinarlo prima/dopo ogni test. Il codice è in Scala, ma questa è solo un'idea generale. Fatemi sapere se questo è ciò che state cercando, quindi elaborerò un po 'di più in una risposta separata. –

risposta

3

Come da suggerimento di Tomasz ho usato un TransactionTemplate per raggiungere i commit dopo ogni operazione di dao. Non esiste un'annotazione @Transactional a livello di classe.

Le voci di controllo di envers vengono inserite prima che il metodo finisca che è proprio quello che mi serviva.

@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

Sono un utente di sostegno di prova delle transazioni di primavera che rotola di nuovo test quando il loro fatto, e grazie al design di Envers, non vengono creati revisioni. Ho creato un hack che sembra permettere a uno di "dire" a envers di fare il suo lavoro, manualmente, prima che la transazione si impegni, ma permette a molla di continuare a rollback.

Questi snippet dovrebbero aiutare. 1. Creare il proprio listener di controllo che sovrascriva il listener di audit envers esistente. Ciò consente l'accesso a un membro statico visibile ai test di unità. C'è probabilmente un modo migliore, ma funziona.

public class AuditEventListenerForUnitTesting extends AuditEventListener { 

    public static AuditConfiguration auditConfig; 

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

modificare la persistence.xml per includere questa nuova classe ascoltatore invece di quello fornito da envers

(ripetere per altri ascoltatori se necessario)

Ora all'interno del test "unità":

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

Avevo bisogno di questo perché ho alcune query JDBC contro le entità di ibernazione unite alle tabelle di versioning.

0

Le altre due soluzioni non hanno funzionato per me, quindi ho usato un altro modo, ho appena creato una nuova transazione e forzato il commit. Ogni volta che ho bisogno di una nuova revisione lo faccio di nuovo.

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

Se si utilizza @Transactional(transactionManager="transactionManager") tuttavia potrebbe bypassare i commit e considerare ogni test come una transazione (quindi non versionning tempo multiple all'interno della stessa prova ...)

2

Questo è fortemente ispirata this previous answer adattata alle lavorare con Envers 4.2.19.Final (JPA 2.0).Questa soluzione non ha bisogno della transazione per eseguire il commit, il che era un requisito nel mio caso.

Innanzitutto creare la seguente implementazione di org.hibernate.integrator.spi.Integrator e inserirlo al 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 
    } 

} 

quindi creare un file META-INF/services/org.hibernate.integrator.spi.Integrator sotto src/test/resources e incollare il nome completo qulified della classe integratore in esso.

nel test, chiamare il metodo DAO, sciacquare la sessione Hibernate e dopo il gioco è fatto dire Envers per procedere:

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

si può quindi verificare il contenuto del vostro DB e, infine, rollback della transazione.

Problemi correlati