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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))