Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm trying to figure out a way to have async before and after statements where the next test cases aren't run until the completion of the action inside of the test case. In my case, it is the creating and dropping a table inside of a database

  val table = TableQuery[BlockHeaderTable]
  val dbConfig: DatabaseConfig[PostgresDriver] = DatabaseConfig.forConfig("databaseUrl")
  val database: Database = dbConfig.db
  before {
    //Awaits need to be used to make sure this is fully executed before the next test case starts
    //TODO: Figure out a way to make this asynchronous 
    Await.result(database.run(table.schema.create), 10.seconds)
  }

  "BlockHeaderDAO" must "store a blockheader in the database, then read it from the database" in {
    //...
  }

  it must "delete a block header in the database" in {
    //...
  }

  after {
    //Awaits need to be used to make sure this is fully executed before the next test case starts
    //TODO: Figure out a way to make this asynchronous
    Await.result(database.run(table.schema.drop),10.seconds)
  }

Is there a simple way I can remove these Await calls inside of my before and after functions?

like image 844
Chris Stewart Avatar asked Sep 10 '16 01:09

Chris Stewart


2 Answers

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)
}
like image 105
Gal Avatar answered Oct 20 '22 01:10

Gal


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.

like image 25
Jeffrey Chung Avatar answered Oct 19 '22 23:10

Jeffrey Chung