spring 测试@TransactionalEvents和@Rollback

hgc7kmma  于 2023-11-16  发布在  Spring
关注(0)|答案(4)|浏览(147)

我一直在测试@TransactionalEvents(Spring 4.2 https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2的一个特性)与我们现有的Spring JUnit测试(通过@TransactionalTestExecutionListener或子类AbstractTransactionalUnit4SpringContextTests运行,但是,似乎有一个被迫的选择-要么在没有@Rollback注解的情况下运行测试,有没有人遇到过一个好方法来测试@TransactionalEvents,同时能够@Rollback测试?

lfapxunr

lfapxunr1#

Stéphane Nicoll是正确的:如果@TransactionalEventListenerTransactionPhase被设置为AFTER_COMMIT,那么使用自动回滚语义进行事务测试就没有任何意义,因为事件永远不会被触发。
换句话说,如果事务从未提交,则无法在事务提交后触发事件。
因此,如果你真的希望事件被触发,你必须让事务被提交(例如,通过使用@Commit注解你的测试方法)。要在提交后进行清理,你应该能够在事务提交后 * 以 isolated 模式使用@Sql执行清理脚本。例如,像下面这样的东西(未经测试的代码)可能对你有用:

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

字符串
此致,
Sam(* Spring TestContext Framework的作者 *)

vx6bjr1n

vx6bjr1n2#

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传播进行修饰,除了测试之外,该方案不修改其他逻辑。

方案二

@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脚本,否则我们会引入提交修改数据的副作用。

eivgtgni

eivgtgni3#

萨姆·布兰南的解决方案几乎适用于亚当的评论。
实际上,用@TransactionalEventListener标注的方法是在测试方法事务提交之后被调用的。这是因为引发事件的调用方法是在逻辑事务中执行的,而不是物理事务。
相反,当调用方法在一个新的物理事务中执行时,用@TransactionalEventListener注解的方法会在正确的时间被调用,即在测试方法事务被提交之前。
另外,我们在测试方法上不需要@Commit,因为我们实际上并不关心这些事务。然而,我们确实需要Sam Brannen解释的@Sql(...)语句来撤销调用方法的已提交更改。
看下面的小例子。
首先是提交事务时调用的侦听器(@TransactionalEventListener的默认行为):

@Component
public class MyListener {

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

字符串
然后是发布上述类监听的事件的application service。请注意,每次调用方法时transactions都被配置为新的物理transactions(有关更多详细信息,请参阅Spring Framework文档):

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


这样做就像一个魅力:-)

l3zydbqr

l3zydbqr4#

只是为了历史记录。我通过模板创建新的交易

@Transactional(propagation = Propagation.NEVER)
@TestPropertySource(properties = {"spring.sql.init.mode=never"}) // I don't use
TestClass {

@SpyBean
private TransactionTemplate transactionTemplate;
@Autowired
private ApplicationEventPublisher publisher;

@Test  
void test() {
transactionTemplate.execute(status -> {
            publisher.publishEvent(event);
            return status;
        });
/** assertions **/
}

字符串

相关问题