Search code examples
unit-testingscalaplayframework-2.0specs2

running multiple tests within the same FakeApplication() in play 2.0 scala


I am trying to learn the unit tests in Play scala, but I am running into some issues. I am trying to run several tests on my models layer like this:

"User Model" should {
    "be created and retrieved by username" in {
        running(FakeApplication()) {
            val newUser = User(username = "weezybizzle",password = "password")
            User.save(newUser)
            User.findOneByUsername("weezybizzle") must beSome
        }
    }
    "another test" in {
        running(FakeApplication()) {
            // more tests involving adding and removing users
        }
    }
}

However when doing things this way, I fail to connect to the database on the second unit test, saying that the connection is closed. I tried to solve this by enclosing all the code in a block that runs on the same fake application, but that didn't work either.

  running(FakeApplication()) {
    "be created and retrieved by username" in {
        val newUser = User(username = "weezybizzle",password = "password")
        User.save(newUser)
        User.findOneByUsername("weezybizzle") must beSome
    }
    "another test" in {
        // more tests involving adding and removing users
    }
  }

Solution

  • The specs2 tests are performed by default in parallel which may cause problems with accessing databases, especially when you rely on the db contents provided by a previous test. So to force sequential testing you have to tell specs2 to do so:

    class ModelSpec extends Specification with Logging {
      override def is = args(sequential = true) ^ super.is
    ...
    }
    

    For tests done in one FakeApplication you can wrap the whole tests in it:

      running(FakeApp) {
        log.trace("Project tests.")
        val Some(project) = Project.findByName("test1")
    
        "Project" should {
    
          "be retrieved by name" in {
            project must beAnInstanceOf[Project]
            project.description must endWith("project")
          }
    

    The whole sample can be found here. That was my first attempt to deal with problems while testing MongoDB with Play! framework.

    The second approach I borrowed from the salat project, which is by the way a very good source of specs examples dealing with MongoDB (although it is not a Play! framework app). You have to define a trait extending Around and Scope, where you can put anything you need to be initialized in an application instance:

    import org.specs2.mutable._
    import org.specs2.execute.StandardResults
    
    import play.api.mvc._
    import play.api.mvc.Results
    import play.api.test._
    import play.api.test.Helpers._
    
    trait FakeApp extends Around with org.specs2.specification.Scope {
    
      val appCfg = Map(
        "first.config.key" -> "a_value",
        "second.config.key" -> "another value"
      )
    
      object FakeApp extends FakeApplication(
          additionalPlugins = Seq("com.github.rajish.deadrope.DeadropePlugin"),
          additionalConfiguration = appCfg
        ) {
        // override val routes = Some(Routes)
      }
    
      def around[T <% org.specs2.execute.Result](test: => T) = running(FakeApp) {
        Logger.debug("Running test ==================================")
        test  // run tests inside a fake application
      }
    }
    

    Edit 2013-06-30:

    In the current version of specs2 the around signature should be:

    def around[T : AsResult](test: => T): Result
    

    End of edit

    Then a test can be written like that:

    class SomeSpec extends Specification { sequential // according to @Eric comment
    
      "A test group" should {
        "pass some tests" in new FakeApp {
          1 must_== 1
        }
    
        "and these sub-tests too" in {
          "first subtest" in new FakeApp {
             success
          }
          "second subtest" in new FakeApp {
             failure
          }
        }
      }
    }
    

    A full sample of such suite can be found here.

    On a final note: It's also good to clean up the test database before starting a suite:

      step {
        MongoConnection().dropDatabase("test_db")
      }