我有一个Spring服务,它可以做类似这样的事情:
@Service
public class MyService {
@Transactional(propagation = Propagation.NEVER)
public void doStuff(UUID id) {
// call an external service, via http for example, can be long
// update the database, with a transactionTemplate for example
}
}
Propagation.NEVER表示在调用该方法时不能有活动事务,因为我们不希望在等待外部服务的应答时阻塞到数据库的连接。
现在,我该如何正确地测试它,然后回滚数据库呢?@测试中的Transactional将不起作用,因为Propagation. NEVER而将出现异常。
@SpringBootTest
@Transactional
public class MyServiceTest {
@Autowired
private MyService myService;
public void testDoStuff() {
putMyTestDataInDb();
myService.doStuff(); // <- fails no transaction should be active
assertThat(myData).isTheWayIExpectedItToBe();
}
}
我可以删除@Transactional,但这样我的数据库将无法在下一次测试中处于一致状态。
目前,我的解决方案是在@AfterEach junit回调中的每次测试后截断数据库的所有表,但这有点笨拙,并且当数据库有多个表时会变得相当慢。
我的问题来了:如何在不截断/使用@Transactional的情况下回滚对数据库所做的更改?
我正在测试的数据库是mariadb和testcontainers,所以一个只适用于mariadb/mysql的解决方案对我来说已经足够了,但是更通用的解决方案会更好!
(另一个我希望在测试中不使用@Transactional的示例:有时我想测试事务边界是否正确地放在代码中,而不是在运行时遇到一些延迟加载异常,因为我在生产代码中的某个地方忘记了@Transactional。
如果有帮助的话,还有一些其他的精确性:
- 我将JPA与Hibernate结合使用
- 当应用程序上下文启动时,使用liquibase创建数据库
其他想法我玩过:
- @脏上下文:这要慢得多,创建一个新的上下文比仅仅截断数据库中的所有表要昂贵得多
- MariaDB保存点:死胡同,这只是一种方式回到数据库的状态内的事务。这将是理想的解决方案IMO如果我可以全球工作
- 尝试摆弄连接,在测试之前在数据源上本地发出
START TRANSACTION
语句,在测试之后发出ROLLBACK
语句:很脏,无法使用
2条答案
按热度按时间mkh04yzy1#
个人意见:
@Transactional
+@SpringBootTest
(在某种程度上)与spring.jpa.open-in-view
是相同的反模式。是的,一开始很容易让事情正常工作,自动回滚也很好,但是它会让你失去很多灵活性和对事务的控制。任何需要手动事务管理的东西都很难用这种方式进行测试。我们最近遇到了一个非常类似的案例,最后我们决定咬紧牙关,改用
@DirtiesContext
。是的,测试需要多花30分钟来运行,但作为一个额外的好处,被测试的服务的行为方式与生产中完全相同,测试更有可能捕捉到任何事务问题。但在进行切换之前,我们考虑使用以下解决方法:
1.创建一个类似于以下内容的接口和服务:
第一个
1.在您的其他服务中,使用
#runWithoutTransaction
-方法 Package 外部http调用,例如:这样,您的产品代码将执行
Propagation.NEVER
检查,并且对于测试,您可以将TransactionService
替换为不具有@Transactional
注解的不同实现,例如:这并不限于
Propagation.NEVER
。其他传播类型也可以用相同的方式实现:最后,如果方法需要返回和/或接受一个值,则
Runnable
参数可以替换为Function
/Consumer
/Supplier
。b5lpy0ml2#
这是一个有点疯狂的想法,但如果你正在使用mysql数据库,那么也许切换到dolt进行测试?
Dolt是一个SQL数据库,你可以像git仓库一样分叉、克隆、分支、合并、推送和拉取。
您可以将其 Package 为testcontainers container,在启动时加载必要的数据,然后在每个测试运行开始时dolt重置。