2016-01-20 20 views
9

Chcę napisać kilka testów integracyjnych dla usługi, która działa zręcznie, a następnie wyczyścić bazę danych PostgreSQL po zwinięciu transakcji, ale nie widzę sposobu, aby to zrobić. Rozumiem, że mogę testować obiekty DBIO, które zostały skomponowane razem i przetworzyć je z powrotem, ale nie wygląda na to, że jest to możliwe, jeśli chcę przetestować na wyższym poziomie abstrakcji.Jak mogę wycofać test integracji ze Slick 3 + Specs2?

W Pseudokod, chcę to zrobić:

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

Na przykład, jeśli mam to (uproszczone z play-silhouette-slick-seed):

class PasswordInfoDAO(db: JdbcBackend#DatabaseDef) { 

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

} 

myślałem, mógłbym napisać cechę ForEach wzdłuż linie na Specs2 Guide, co daje to ogólny przykład:

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

Więc dla śliskiego, próbowałem to:

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 

} 

Potem planował użyć coś jak to:

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

Problemem jest to, że zręczny 3 zignoruje moją sesję (wg wzoru) a zamiast tego użyć puli sesji, więc mój roll-back nic nie robi. Myślę, że Slick ma nadzieję, że powinieneś go użyć na poziomie DBIOActions, które można skomponować razem i ewentualnie wykonać w różnych kontekstach. Slick 2 miał sposób kontrolowania sesji z .withSession, ale został usunięty.

Czy jest jedyną opcją tworzenia, migrowania i upuszczania testowej bazy danych przy każdym teście?

Odpowiedz

5

Oto częściowa odpowiedź. Wydaje się, że albo niemożliwe, albo co najmniej bardzo niewskazane, aby wycofać transakcję, docierając do JDBC. Zamiast tego przepisałem repozytoria, aby zwracały DBIO zamiast moich obiektów biznesowych. Jest to monadyczna operacja wiązania DBIO, która zajmuje się logiką transakcji, więc to naprawdę jedyny sposób, aby wycofać coś z powrotem.

class MyRepository { 

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

mam funkcji, które wiąże się niepożądanego działania na „fałszywe” wyjątek, a następnie powraca na przyszłe w wyniku pierwotnego działania i odrzuca się wyjątek:

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

}

Więc mogę użyć tego ze specyfikacji:

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

Jestem pewien, że istnieje również sposób, aby napisać ten mo Czyściutko dla specs2 jako cecha ForEach lub coś podobnego.

Wzięłam te idee z this i this

Powiązane problemy