How can I roll back an integration test with Slick 3 + Specs2?
Here's a partial answer. It seems to be either impossible or at least very inadvisable to roll back a transaction by reaching down to JDBC. So instead I rewrote the repositories to return DBIOs instead of my business objects. It's the DBIO monadic bind operation that takes care of transaction logic, so that's really the only way to roll something back.
class MyRepository {
def add(whatever: String): dbio.DBIOAction[Int, NoStream, Write with Write] = {
// return a DBIOAction
}
}
I have a function that binds an arbitrary action to a "fake" exception, and then returns the Future result of the original action and discards the exception:
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]
}
}
So then I can use this from a spec:
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 ...
I'm sure there's also a way to write this more cleanly for specs2 as a ForEach trait or something similar.
I took these ideas from this and this