Loading test data with Play Framework Evolutions

comment 1
Dev

In a previous article I described how to load test data that your ScalaTest Play Framework functional tests might need using Play Framework’s Evolutions. This made use of the SimpleEvolutionsReader class and defining evolutions in the test setup code.

Recently I wanted to also load some test data from a file and so turned to the ClassLoaderEvolutionsReader class which loads resources from the class path.

The trouble was I wanted to apply the schema from my standard evolution files first and then load the test data. The ClassLoaderEvolutionsReader requires evolutions revisions to start at 1 which would conflict with the standard application evolutions already applied.

So I wrote a custom SingleRevisionClassLoaderEvolutionsReader that reads a single revision from the class path.


import play.api.db.evolutions.{ClassLoaderEvolutionsReader, Evolution}
import play.api.libs.Collections
/***
* Evolutions reader that reads a single revision from the class path.
*
* @param revision the revision number to load
* @param prefix A prefix that gets added to the resource file names
*/
class SingleRevisionClassLoaderEvolutionsReader(val revision: Int, val prefix: String) extends ClassLoaderEvolutionsReader(prefix = prefix) {
override def evolutions(db: String): Seq[Evolution] = {
val upsMarker = """^#.*!Ups.*$""".r
val downsMarker = """^#.*!Downs.*$""".r
val UPS = "UPS"
val DOWNS = "DOWNS"
val UNKNOWN = "UNKNOWN"
val mapUpsAndDowns: PartialFunction[String, String] = {
case upsMarker() => UPS
case downsMarker() => DOWNS
case _ => UNKNOWN
}
val isMarker: PartialFunction[String, Boolean] = {
case upsMarker() => true
case downsMarker() => true
case _ => false
}
loadResource(db, revision).map { stream =>
val script = scala.io.Source.fromInputStream(stream).mkString
val parsed = Collections.unfoldLeft(("", script.split('\n').toList.map(_.trim))) {
case (_, Nil) => None
case (context, lines) => {
val (some, next) = lines.span(l => !isMarker(l))
Some((next.headOption.map(c => (mapUpsAndDowns(c), next.tail)).getOrElse("" -> Nil),
context -> some.mkString("\n")))
}
}.reverse.drop(1).groupBy(i => i._1).mapValues { _.map(_._2).mkString("\n").trim }
Evolution(
revision,
parsed.getOrElse(UPS, ""),
parsed.getOrElse(DOWNS, ""))
}.toList
}
}
object SingleRevisionClassLoaderEvolutionsReader {
def apply(revision: Int, prefix: String = "") = new SingleRevisionClassLoaderEvolutionsReader(revision, prefix)
}

You can then place your evolution files in /test/resources/evolutions/default/ and apply them after your standard evolutions in your test setup, for example, if your test data was in a file called 100.sql :

// Load the database schema
Evolutions.applyEvolutions(db)

// Load the test data
Evolutions.applyEvolutions(db, SingleRevisionClassLoaderEvolutionsReader(revision = 100))

1 Comment

  1. Antoine says

    Thanks for this shared information ! I was searching a way to load test data in database with Evolutions and your blog came up !
    It will help me doing more easier integration tests now 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *