【问题标题】:How can I roll back an integration test with Slick 3 + Specs2?如何使用 Slick 3 + Specs2 回滚集成测试?
【发布时间】:2016-04-26 14:50:28
【问题描述】:

我想为运行流畅的服务编写一些集成测试,然后通过回滚事务来清理 postgresql 数据库,但我看不到这样做的方法。我知道我可以测试组合在一起的 DBIO 对象并将它们回滚,但如果我想在更高的抽象级别上进行测试,这似乎是不可能的。

在伪代码中,我想这样做:

StartDbTransaction() // setup
DoSomethingInDB() 
AssertSomething() 
RollBackDbTransaction() // teardown

例如,如果我有这个(从play-silhouette-slick-seed 简化):

class PasswordInfoDAO(db: JdbcBackend#DatabaseDef) {

    // ...
    def remove(loginInfo: LoginInfo): Future[Unit] =
        db.run(passwordInfoSubQuery(loginInfo).delete).map(_ => ())

}

我想我可以按照Specs2 Guide 的方式编写一个 ForEach 特征,这给出了一个通用示例:

// a transaction with the database
trait Transaction

trait DatabaseContext extends ForEach[Transaction] {
    // you need to define the "foreach" method
    def foreach[R: AsResult](f: Transaction => R): Result = {
        val transaction = openDatabaseTransaction
        try AsResult(f(transaction))
        finally closeDatabaseTransaction(transaction)
    }

    // create and close a transaction
    def openDatabaseTransaction: Transaction = ???

    def closeDatabaseTransaction(t: Transaction) = ???
}

class FixtureSpecification extends mutable.Specification with DatabaseContext {
    "example 1" >> { t: Transaction =>
        println("use the transaction")
        ok
    }
    "example 2" >> { t: Transaction =>
        println("use it here as well")
        ok
    }
}

所以对于 slick,我尝试了这个:

override def foreach[R: AsResult](f: JdbcBackend#DatabaseDef => R): Result = {

    val db = dbConfig.db
    val session = db.createSession()
    session.conn.setAutoCommit(false)
    val result = AsResult(f(db))
    session.conn.rollback()
    result

}

然后我打算像这样使用它:

class PasswordInfoDAOSpec(implicit ee: ExecutionEnv)
  extends Specification with DatabaseContext {

    "password" should {
       "be removed from db" in { db =>

        // arrange
        db.run(...) // something to set up the database

        // act
        PasswordInfoDAO(db).remove(loginInfo).await

        // assert
        PasswordInfoDAO(db).find(loginInfo) must be None.await
      }
   }
}

问题是 slick 3 将忽略我的会话(按设计),而是使用会话池,因此我的回滚不会做任何事情。我认为 Slick 期望您应该在 DBIOActions 级别使用它,这些 DBIOActions 可以组合在一起并可能在不同的上下文中执行。 Slick 2 有办法用.withSession 控制会话,但它被删除了。

是否是每次测试都创建、迁移和删除测试数据库的唯一选项?

【问题讨论】:

    标签: scala playframework slick specs2


    【解决方案1】:

    这是部分答案。通过使用 JDBC 来回滚事务似乎是不可能的,或者至少是非常不可取的。因此,我改写了存储库以返回 DBIO 而不是我的业务对象。处理事务逻辑的是 DBIO monadic 绑定操作,所以这确实是回滚某些东西的唯一方法。

    class MyRepository {
    
      def add(whatever: String): dbio.DBIOAction[Int, NoStream, Write with Write] = {
           // return a DBIOAction
      }
    }
    

    我有一个函数可以将任意动作绑定到“假”异常,然后返回原始动作的 Future 结果并丢弃异常:

    case class IntentionalRollbackException[R](successResult: R) extends Exception("Rolling back transaction")
    
    def runWithRollback[R, S <: slick.dbio.NoStream, E <: slick.dbio.Effect](action: DBIOAction[R, S, E]): Future[R] = {
    
      val block = action.flatMap(r => DBIO.failed(new IntentionalRollbackException(r)))
    
      val tryResult = dbConfig.db.run(block.transactionally.asTry)
    
      // not sure how to eliminate these casts from Any
      tryResult.map {
        case Failure(IntentionalRollbackException(successResult)) => successResult.asInstanceOf[R]
        case Failure(t) => throw t
        case Success(r) => r.asInstanceOf[R]
      }
    

    }

    那么我可以从规范中使用它:

    val insertAction1 = new MyRepository().add("whatever 1").withPinnedSession
    val insertAction2 = new MyRepository().add("whatever 2").withPinnedSession
    val actions = insertAction1 andThen insertAction2
    val result = Await.result(runWithRollback(action), 5.seconds)
    result must be ...
    

    我相信还有一种方法可以更简洁地将 specs2 编写为 ForEach 特征或类似的东西。

    我从thisthis 那里得到了这些想法

    【讨论】:

      【解决方案2】:

      在谷歌搜索如何在 Slick 中回滚后,我来到了这个页面,最终发现 Slick 3 提供了对底层 JDBC 连接的访问​​,允许它在不需要 DBIO 失败的情况下完成:

        val rollback: DBIO[Unit] = SimpleDBIO(_.connection.rollback)
        def runAndRollback[A](dbio: DBIO[A]): DBIO[A] = dbio.andFinally(rollback).transactionally
      

      【讨论】:

        【解决方案3】:

        您必须将所有逻辑放入 DBIO,然后包含一个 DBIO.failed 步骤来执行回滚。请参阅https://github.com/slick/slick/commit/6caaea3a8a888d54dc51463bc0e1725191b9721a,添加 3.2 的“回滚”文档(但 IIUC 从 3.0 开始就是正确的)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-01-28
          • 1970-01-01
          • 2011-11-28
          • 1970-01-01
          • 2023-02-08
          • 2016-06-07
          • 2011-07-07
          • 1970-01-01
          相关资源
          最近更新 更多