测试@TransactionalEvents 和@Rollback

Testing @TransactionalEvents and @Rollback

我一直在尝试使用我们现有的 Spring JUnit 测试 (运行通过 @TransactionalTestExecutionListener 或子类化 AbstractTransactionalUnit4SpringContextTests 但是,似乎有一个被迫的选择——要么 运行 没有 @Rollback 注释的测试,要么事件不触发。有没有人遇到过在能够进行@Rollback 测试的同时测试@TransactionalEvents 的好方法?

Stéphane Nicoll 是正确的:如果 @TransactionalEventListenerTransactionPhase 设置为 AFTER_COMMIT,那么使用自动回滚语义进行事务测试没有任何意义,因为事件永远不会被触发。

换句话说,如果事务从未提交,则无法在事务提交后触发事件。

因此,如果您确实希望触发事件,则必须提交事务(例如,通过使用 @Commit 注释您的测试方法)。要在提交后进行清理,您应该能够在 isolated 模式下使用 @Sql 来执行清理脚本 after 事务已提交.例如,类似以下内容(未经测试的代码)可能适合您:

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

此致,

Sam(Spring TestContext Framework 的作者)

Sam Brannen 的解决方案几乎适用于亚当的评论。

实际上用@TransactionalEventListener注解的方法是在提交测试方法事务后调用的。这是因为引发事件的调用方法是在逻辑事务而不是物理事务中执行的。

相反,当在新的物理事务中执行调用方法时,会在正确的时间调用带有 @TransactionalEventListener 注释的方法,即在提交测试方法事务之前。

此外,我们不需要 @Commit 测试方法,因为我们实际上并不关心这些事务。但是,我们确实需要 Sam Brannen 解释的 @Sql(...) 语句来撤消调用方法的已提交更改。

请参阅下面的小示例。

首先是提交事务时调用的侦听器(@TransactionalEventListener 的默认行为):

@Component
public class MyListener {

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

然后是发布上面监听的事件的应用服务class。请注意,每次调用方法时,事务都配置为新的物理事务(有关详细信息,请参阅 Spring Framework doc):

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

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

最后是 Sam Brannen 提出的测试方法,但没有 @Commit此时不需要的注释:

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

这种方式很有魅力:-)

Marco 的解决方案有效,但将 REQUIRES_NEW 传播添加到业务代码中并不总是可以接受。这会修改业务流程行为。

所以我们应该假设我们只能更改测试部分。

溶液 1

@TestComponent // can be used with spring boot
public class TestApplicationService {

    @Autowired
    public MyApplicationService service;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void doSomething() {
        service.doSomething();
   }
}

这将实际服务包装到可以用 REQUIRES_NEW 传播修饰的测试组件中。此解决方案不修改除测试之外的其他逻辑。

解决方案 2

@Transactional
@Sql(scripts = "/cleanup.sql", executionPhase = AFTER_TEST_METHOD,
     config = @SqlConfig(transactionMode = TransactionMode.ISOLATED))
@Test
public void test() { 
    MyApplicationService target = // ...
    target.doSomething();
    TestTransaction.flagForCommit(); //Spring-test since 4.1 - thx for Sam Brannen
    TestTransaction.end();
    // the event is now received by MyListener
    // assertions on the side effects of MyListener
    // ...
}

这是最简单的解决方案。我们可以结束测试事务并将其标记为提交。这会强制处理事务事件。如果测试更改数据,则必须指定清理 sql 脚本,否则我们会引入已提交修改数据的副作用。