Testing @TransactionalEvents and @Rollback

房东的猫 提交于 2019-12-01 08:57:55

Stéphane Nicoll is correct: if the TransactionPhase for your @TransactionalEventListener is set to AFTER_COMMIT, then having a transactional test with automatic rollback semantics doesn't make any sense because the event will never get fired.

In other words, there is no way to have an event fired after a transaction is committed if that transaction is never committed.

So if you really want the event to be fired, you have to let the transaction be committed (e.g., by annotating your test method with @Commit). To clean up after the commit, you should be able to use @Sql in isolated mode to execute cleanup scripts after the transaction has committed. For example, something like the following (untested code) might work for you:

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

Regards,

Sam (author of the Spring TestContext Framework)

Sam Brannen's solution almost works with regard to adam's comment.

Actually the methods annotated with @TransactionalEventListener are called after the test method transaction is committed. This is so because the calling method, which raises the event, is executing within a logical transaction, not a physical one.

Instead, when the calling method is executed within a new physical transaction, then the methods annotated with @TransactionalEventListener are invoked at the right time, i.e., before the test method transaction is committed.

Also, we don't need @Commit on the test methods, since we actually don't care about these transactions. However, we do need the @Sql(...) statement as explained by Sam Brannen to undo the committed changes of the calling method.

See the small example below.

First the listener that is called when the transaction is committed (default behavior of @TransactionalEventListener):

@Component
public class MyListener {

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

Then the application service that publishes the event listened to by the above class. Notice that the transactions are configured to be new physical ones each time a method is invoked (see the Spring Framework doc for more details):

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

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

Finally the test method as proposed by Sam Brannen but without the @Commitannotation which is not needed at this point:

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

This way it works like a charm :-)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!