使用Acolyte模拟JDBC驱动:如何测试事务(提交/回滚)行为?
嘿,我之前在Scala项目里用Acolyte测试JDBC事务逻辑时也遇到过类似的问题,给你分享几个实用的方案,帮你验证commit/rollback是否被正确调用:
方案1:用Acolyte原生Handler直接捕获事务调用
Acolyte的MockConnection自带了事务相关的回调钩子,不需要自己写复杂的包装类,直接设置withCommitHandler和withRollbackHandler就能跟踪调用状态:
import acolyte.jdbc.MockConnection import scala.util.Try // 测试成功场景下的commit调用 test("executeUnitOfWorkNoResult triggers commit on successful execution") { var commitInvoked = false var rollbackInvoked = false // 配置MockConnection的事务回调 val mockConn = MockConnection.empty .withCommitHandler(_ => { commitInvoked = true; () }) .withRollbackHandler(_ => { rollbackInvoked = true; () }) // 执行你的UnitOfWork方法 val result = executeUnitOfWorkNoResult(List("INSERT INTO users VALUES (1, 'Alice')"))(() => mockConn) // 断言结果和调用状态 assert(result.isSuccess) assert(commitInvoked, "Commit should have been called after successful work") assert(!rollbackInvoked, "Rollback shouldn't be called in success case") } // 测试失败场景下的rollback调用 test("executeUnitOfWorkNoResult triggers rollback on failure") { var commitInvoked = false var rollbackInvoked = false // 创建会抛出异常的MockConnection,模拟SQL执行失败 val failingConn = MockConnection.empty .withCommitHandler(_ => { commitInvoked = true; () }) .withRollbackHandler(_ => { rollbackInvoked = true; () }) // 让执行SQL时抛出异常 .withStatementHandler(_ => throw new java.sql.SQLException("Test failure")) val result = executeUnitOfWorkNoResult(List("INSERT INTO users VALUES (1, 'Alice')"))(() => failingConn) assert(result.isFailure) assert(rollbackInvoked, "Rollback should have been called after work failed") assert(!commitInvoked, "Commit shouldn't be called in failure case") }
这个方案最简洁,完全利用Acolyte的API,不需要额外的包装类。
方案2:自定义跟踪Connection(适合更复杂的场景)
如果需要跟踪更多JDBC操作(比如setAutoCommit、保存点等),可以自己写一个包装类,把Connection的所有事务相关方法都记录下来:
import java.sql.{Connection, SQLException, Statement} import scala.collection.mutable.ListBuffer class TrackingConnection(delegate: Connection) extends Connection { // 记录所有事务相关调用的日志 val callLog: ListBuffer[String] = ListBuffer.empty override def commit(): Unit = { callLog += "commit" delegate.commit() } override def rollback(): Unit = { callLog += "rollback" delegate.rollback() } override def setAutoCommit(autoCommit: Boolean): Unit = { callLog += s"setAutoCommit($autoCommit)" delegate.setAutoCommit(autoCommit) } // 其他Connection方法全部委托给底层的delegate override def close(): Unit = delegate.close() override def createStatement(): Statement = delegate.createStatement() // ... 这里需要实现Connection接口的所有剩余方法,或者用动态代理简化(比如用ByteBuddy) }
然后在测试里用这个包装类包裹Acolyte的MockConnection:
test("verify full transaction flow including setAutoCommit") { val mockConn = MockConnection.empty val trackingConn = new TrackingConnection(mockConn) val result = executeUnitOfWorkNoResult(List("INSERT ..."))(() => trackingConn) assert(result.isSuccess) // 断言事务流程:先关闭自动提交,执行SQL,然后commit assert(trackingConn.callLog.contains("setAutoCommit(false)")) assert(trackingConn.callLog.contains("commit")) }
额外提示
如果你的executeUnitOfWorkNoResult方法里包含事务边界的处理(比如默认关闭autoCommit),一定要记得验证这一步,因为这是事务生效的前提。另外,如果你用ScalaTest或Specs2这类测试框架,可以用它们的匹配器让断言更易读,比如:
import org.scalatest.matchers.should.Matchers._ commitInvoked shouldBe true trackingConn.callLog should contain("setAutoCommit(false)")
内容的提问来源于stack exchange,提问作者Flo
相关产品推荐
相关产品推荐

