问题
I've been trying to test out @TransactionalEvents
(a feature of Spring 4.2 https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2) with our existing Spring JUnit Tests (run via either @TransactionalTestExecutionListener
or subclassing AbstractTransactionalUnit4SpringContextTests
but, it seems like there's a forced choice -- either run the test without a @Rollback
annotation, or the events don't fire. Has anyone come across a good way to test @TransactionalEvents while being able to @Rollback tests?
回答1:
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)
回答2:
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 @Commit
annotation 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 :-)
来源:https://stackoverflow.com/questions/36536466/testing-transactionalevents-and-rollback