2016-04-10 5 views
2

Ich habe versucht, @TransactionalEvents zu testen (eine Funktion von Spring 4.2 https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2) mit unseren bestehenden Feder JUnit-Tests (lief entweder über @TransactionalTestExecutionListener oder Subklassifizieren AbstractTransactionalUnit4SpringContextTests aber, so scheint es, als gäbe es eine erzwungene Wahl ist - - entweder den Test ohne @Rollback Anmerkung ausführen oder die Ereignisse nicht ausgelöst hat über eine gute Art und Weise jemand kommen @TransactionalEvents zu testen, während der Lage, Tests @RollbackTesting @TransactionalEvents und @Rollback

+0

Wenn Sie die Transaktion Rollback, die den Test verwendet, und Sie wollen sicherstellen, dass ein bestimmtes Stück Code machen auf commit aufgerufen werden , du sollst Ich würde das anders testen. Ich würde argumentieren, dass Sie vielleicht das Framework testen und nicht Ihren eigenen Code. –

+0

Nicht sicher, stimme ich zu, aber ich werde darüber nachdenken. Mit @Rollback wird die Datenbank zurückgesetzt, um den ursprünglichen Teststatus wiederherzustellen (um sicherzustellen, dass die Wahrscheinlichkeit einer Interaktion zwischen Tests geringer ist). – adam

+0

Ich weiß was es macht. Das Problem besteht darin, dass Sie ein Ereignis anfordern, das durch die Transaktion ausgeführt wurde. Es hat nicht. –

Antwort

3

Stéphane Nicoll ist richtig: wenn die TransactionPhase für.? Ihr @TransactionalEventListener ist auf AFTER_COMMIT gesetzt, dann macht ein Transaktionstest mit automatischer Rollback-Semantik keinen Sinn, weil das Ereignis nie kommen wird gefeuert.

Mit anderen Worten, es gibt keine Möglichkeit, ein Ereignis ausgelöst zu haben, nachdem eine Transaktion festgeschrieben wurde, wenn diese Transaktion niemals ausgeführt wird.

Wenn Sie also wirklich möchten, dass das Ereignis ausgelöst wird, müssen Sie die Transaktion festschreiben lassen (z. B. indem Sie Ihre Testmethode mit @Commit annotieren). Um nach dem Festschreiben aufzuräumen, sollten Sie @Sql in isolierten Modus verwenden können, um Bereinigungsskripts nach auszuführen, die die Transaktion festgeschrieben hat. Zum Beispiel so etwas wie die folgenden (ungetestet Code) könnte für Sie arbeiten:

@Transactional 
@Commit 
@Sql(scripts = "/cleanup.sql", executionPhase = AFTER_TEST_METHOD, 
    config = @SqlConfig(transactionMode = TransactionMode.ISOLATED)) 
@Test 
public void test() { /* ... */ } 

Grüße,

Sam (Autor des Frühlings Testcontext Rahmen)

+0

Ok, danke. Während ich daran gearbeitet habe, ist ein Teil meines Missverständnisses, dass verschachtelte \ @Transactional-Aufrufe unabhängig von der Weiterleitung das Ereignis immer noch halten, bis die höchste \ @Transactional-Methode abgeschlossen ist. – adam

1

Sam Brannen-Lösung funktioniert fast mit in Bezug auf adams Kommentar.

Tatsächlich werden die mit @TransactionalEventListener annotierten Methoden aufgerufen, nachdem die Testmethodentransaktion festgeschrieben wurde. Dies ist so, weil die aufrufende Methode, die das Ereignis auslöst, innerhalb einer logischen Transaktion, nicht einer physischen ausgeführt wird.

Wenn die aufrufende Methode innerhalb einer neuen physischen Transaktion ausgeführt wird, werden die mit @TransactionalEventListener annotierten Methoden stattdessen zum richtigen Zeitpunkt aufgerufen, d. H. Bevor die Testmethodentransaktion festgeschrieben wird.

Auch brauchen wir nicht @Commit auf die Testmethoden, da uns diese Transaktionen eigentlich egal sind. Allerdings benötigen wir die Anweisung @Sql(...), wie von Sam Brannen erklärt, die festgeschriebenen Änderungen der aufrufenden Methode rückgängig zu machen.

Siehe das kleine Beispiel unten.

Zuerst wird der Zuhörer, die aufgerufen wird, wenn die Transaktion (Standardverhalten von @TransactionalEventListener) begangen wird:

@Component 
public class MyListener { 

    @TransactionalEventListener 
    public void when(MyEvent event) { 
     ... 
    } 
} 

Dann wird die Anwendung Dienst, der das Ereignis durch die obige Klasse angehört veröffentlicht.Beachten Sie, dass die Transaktionen sind so konfiguriert, um neue physische jedesmal, wenn eine Methode aufgerufen wird (siehe Spring Framework doc für weitere Details):

@Service 
@Transactional(propagation = Propagation.REQUIRES_NEW) 
public class MyApplicationService { 

    public void doSomething() { 
     // ... 
     // publishes an instance of MyEvent 
     // ... 
    } 
} 

Schließlich wird das Testverfahren, wie Sam Brannen vorgeschlagen, aber ohne die @Commit Anmerkung, die nicht ist benötigt an dieser Stelle:

@Transactional 
@Sql(scripts = "/cleanup.sql", executionPhase = AFTER_TEST_METHOD, 
    config = @SqlConfig(transactionMode = TransactionMode.ISOLATED)) 
@Test 
public void test() { 
    MyApplicationService target = // ... 
    target.doSomething(); 
    // the event is now received by MyListener 
    // assertions on the side effects of MyListener 
    // ... 
} 

diese Weise ist es wie ein Zauber funktioniert :-)