I want to write some integration tests for a service that runs slick and then clean a postgresql database up afterward up by rolling back a transaction, but I don't see a way to do it. I understand that I can test DBIO objects which have been composed together and roll them back, but it doesn't look like it's possible if I want to test at a higher level of abstraction.
In pseudocode, I want to do this:
StartDbTransaction() // setup
DoSomethingInDB()
AssertSomething()
RollBackDbTransaction() // teardown
For example, if I have this (simplified from the play-silhouette-slick-seed):
class PasswordInfoDAO(db: JdbcBackend#DatabaseDef) {
// ...
def remove(loginInfo: LoginInfo): Future[Unit] =
db.run(passwordInfoSubQuery(loginInfo).delete).map(_ => ())
}
I thought I could write a ForEach trait along the lines of the Specs2 Guide, which gives this a generic example:
// 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
}
}
So for slick, I tried this:
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
}
Then I planned to use it sort of like this:
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
}
}
}
The problem is that slick 3 will ignore my session (by design) and instead use a session pool, so my roll-back doesn't do anything. I think that Slick has an expectation that you should use it at the level of DBIOActions which can be composed together and possibly executed in different contexts. Slick 2 had a way to control the session with .withSession
, but it was removed.
Is the only option to create, migrate and drop a test database with each test?