2016-01-20 8 views
9

サービスを統合的にテストし、トランザクションをロールバックしてpostgresqlデータベースをきれいにするサービスを作成したいのですが、それを行う方法はありません。私は、一緒に構成されたDBIOオブジェクトをテストすることができると理解していますが、より抽象度の高いレベルでテストしたい場合は可能性がありません。Slick 3 + Specs2で統合テストをロールバックするにはどうしたらいいですか?

は擬似コードでは、私はこれをしたい:

class PasswordInfoDAO(db: JdbcBackend#DatabaseDef) { 

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

} 

は私が一緒にするForEach形質を書くと考えています。たとえば

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

、私はこの(play-silhouette-slick-seedから簡体字)がある場合本に一般的な例を示しますSpecs2 Guide、行:

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

だから、滑らかなため、私はこれを試してみました:

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 
     } 
    } 
} 

問題はスリック3(設計によって)私のセッションを無視し、代わりに使用することですセッションプールなので、私のロールバックは何もしません。私は、SlickはあなたがDBIOActionsのレベルでそれを使うべきであると期待していると思っています。これは一緒に構成され、おそらく異なるコンテキストで実行されることがあります。 Slick 2は.withSessionでセッションを制御する方法がありましたが、削除されました。

テストごとにテストデータベースを作成、移行、削除する唯一のオプションはありますか?

答えて

5

ここに部分的な答えがあります。 JDBCに達することでトランザクションをロールバックすることは不可能か、少なくともお勧めできません。代わりに、ビジネスオブジェクトの代わりにDBIOを返すようにリポジトリを書き直しました。トランザクションロジックを処理するのはDBIOモナドのバインド操作なので、実際には何かをロールバックする唯一の方法です。

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] 
    } 

}

class MyRepository { 

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

Iは「偽」例外に任意のアクションを結合し、元のアクションの将来の結果を返し、例外を破棄する機能を有しますだから私は仕様からこれを使うことができます:

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 ... 

私はこのmoを書く方法もあると確信していますForEachの特性などのspecs2をきれいに再確認してください。

私はあなたがDBIOSにすべてのあなたのロジックを配置する必要があり、その後、ロールバックを実行するためのDBIO.failedステップを含んでthisthis

関連する問題