如何使用Slick 3 + Specs2回滚集成测试?

mik*_*dge 9 scala playframework specs2 slick

我想为运行光滑的服务编写一些集成测试,然后通过回滚事务清理postgresql数据库,但是我没有办法做到这一点.我知道我可以测试已经组合在一起的DBIO对象并将它们回滚,但是如果我想在更高的抽象级别进行测试,它看起来不太可能.

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

StartDbTransaction() // setup
DoSomethingInDB() 
AssertSomething() 
RollBackDbTransaction() // teardown
Run Code Online (Sandbox Code Playgroud)

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

class PasswordInfoDAO(db: JdbcBackend#DatabaseDef) {

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

}
Run Code Online (Sandbox Code Playgroud)

我以为我可以按照Specs2指南编写一个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
    }
}
Run Code Online (Sandbox Code Playgroud)

所以对于光滑,我试过这个:

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

}
Run Code Online (Sandbox Code Playgroud)

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

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
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

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

是每次测试创建,迁移和删除测试数据库的唯一选择吗?

mik*_*dge 6

这是一个部分答案.通过深入到JDBC来回滚事务似乎是不可能的或者至少是非常不可取的.因此,我重写了存储库以返回DBIO而不是我的业务对象.这是DBIO monadic绑定操作,负责处理事务逻辑,因此这是回滚的唯一方法.

class MyRepository {

  def add(whatever: String): dbio.DBIOAction[Int, NoStream, Write with Write] = {
       // return a DBIOAction
  }
}
Run Code Online (Sandbox Code Playgroud)

我有一个函数将任意操作绑定到"假"异常,然后返回原始操作的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]
  }
Run Code Online (Sandbox Code Playgroud)

}

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

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 ...
Run Code Online (Sandbox Code Playgroud)

我确信还有一种方法可以将specs2更清晰地写成ForEach特性或类似的东西.

我从这个这个想法中提出了这些想法