Async before and after for creating and dropping scala slick tables in scalatest

The following is the testing approach that Dennis Vriend takes in his slick-3.2.0-test project.

First, define a dropCreateSchema method. This method attempts to create a table; if that attempt fails (because, for example, the table already exists), it drops, then creates, the table:

def dropCreateSchema: Future[Unit] = {
  val schema = BlockHeaderTable.schema
  db.run(schema.create)
    .recoverWith {
      case t: Throwable =>
        db.run(DBIO.seq(schema.drop, schema.create))
    }
}

Second, define a createEntries method that populates the table with some sample data for use in each test case:

def createEntries: Future[Unit] = {
  val setup = DBIO.seq(
    // insert some rows
    BlockHeaderTable ++= Seq(
      BlockHeaderTableRow(/* ... */),
      // ...
    )
  ).transactionally
  db.run(setup)
}

Third, define an initialize method that calls the above two methods sequentially:

def initialize: Future[Unit] = for {
  _ <- dropCreateSchema
  _ <- createEntries
} yield ()

In the test class, mix in the ScalaFutures trait. For example:

class TestSpec extends FlatSpec
  with Matchers
  with ScalaFutures
  with BeforeAndAfterAll
  with BeforeAndAfterEach {

  // ...
}

Also in the test class, define an implicit conversion from a Future to a Try, and override the beforeEach method to call initialize:

implicit val timeout: Timeout = 10.seconds

implicit class PimpedFuture[T](self: Future[T]) {
  def toTry: Try[T] = Try(self.futureValue)
}

override protected def beforeEach(): Unit = {
  blockHeaderRepo.initialize // in this example, initialize is defined in a repo class
    .toTry recover {
      case t: Throwable =>
        log.error("Could not initialize the database", t)
    } should be a 'success
}

override protected def afterAll(): Unit = {
  db.close()
}

With the above pieces in place, there is no need for Await.


Unfortunately, @Jeffrey Chung's solution hanged for me (since futureValue actually awaits internally). This is what I ended up doing:

import org.scalatest.{AsyncFreeSpec, FutureOutcome}
import scala.concurrent.Future

class TestTest extends AsyncFreeSpec /* Could be any AsyncSpec. */ {
  // Do whatever setup you need here.
  def setup(): Future[_] = ???
  // Cleanup whatever you need here.
  def tearDown(): Future[_] = ???
  override def withFixture(test: NoArgAsyncTest) = new FutureOutcome(for {
    _ <- setup()
    result <- super.withFixture(test).toFuture
    _ <- tearDown()
  } yield result)
}