Month: January 2018

  • Loading test data with Play Framework Evolutions

    Loading test data with Play Framework Evolutions

    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))