Question

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
    }
  }
Was it helpful?

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

OTHER TIPS

While doing integration testing/running test suites, we ran into expections like "The CacheManager has been shut down. It can no longer be used" or "SQLException: Attempting to obtain a connection from a pool that has already been shutdown". They were all related to restart the app after each test. We finally made a rather simple trait, that will check for a running FakeApplication before each test, and start one only, if required.

trait SingleInstance extends BeforeExample {
    def before() {
        if (Play.unsafeApplication == null) Play.start(AppWithTestDb)
    }
}

object AppWithTestDb extends FakeApplication(additionalConfiguration = 
    Map("db.default.url" -> "jdbc:mysql://localhost/test_db")
)

And then in the test:

class SampleSpec extends PlaySpecification with SingleInstance {
    "do something" should {
        "result in something" in {
        }
    }
}

This will work for Play 2.3 as well as Play 2.4

A somewhat cleaner approach

import play.api.test._

trait ServerSpec {

  implicit val app: FakeApplication = FakeApplication()
  implicit def port: Port = Helpers.testServerPort

  val server = TestServer(port, app)
}

And then use it with

class UsersSpec extends PlaySpecification with Results with ServerSpec {

  "Users Controller" should {

    step(server.start())

    "get users" in {
      val result = Users.query().apply(FakeRequest())

      val json = contentAsJson(result)
      val stat = status(result)

      stat mustEqual 200
    }

    step(server.stop())
  }
}

In order to test your code against a database, in the case your testing it using the provided in-mem one, you should tell it in the running call:

FakeApplication(additionalConfiguration = inMemoryDatabase())

In a certain manner, this'll force your database to start and stop around the inner block execution (whether it is single or composed)

EDIT

Due to the comment saying that you're using a mongodb, I'd recommend you to read this blog wherein I'm talking about a small plugin I wrote to enable a mongodb server to start embedded-like.

What we'll be done is (by enabling the plugin) to start and stop a mongodb at the same time of the application.

It could help you...

However regarding the initial question the problem shouldn't come from the running or the FakeApplication, unless Play-Salat or any other related plugin is doing bad connections or caching or ...

This kind of parallel testing problem happen when using running method in many case. But this is already fixed in play2.1. Here is how to fix. If you want to use this running in play2.0.x, you should make trait like this:

trait TestUtil {
  /**
   * Executes a block of code in a running application.
   */
  def running[T](fakeApp: FakeApplication)(block: => T): T = {
     synchronized {
      try {
        Play.start(fakeApp)
        block
      } finally {
        Play.stop()
        play.core.Invoker.system.shutdown()
        play.core.Invoker.uninit()
      }
    }
  }

  /**
   * Executes a block of code in a running server.
   */
  def running[T](testServer: TestServer)(block: => T): T = {
    synchronized {
      try {
        testServer.start()
        block
      } finally {
        testServer.stop()
        play.core.Invoker.system.shutdown()
        play.core.Invoker.uninit()
      }
    }
  }
}

And you can use the following:

class ModelSpec extends Specification with TestUtil {
    "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
            }
        }
    }
    ....

The best way I found to run a single test class FakeApplication by Scala, is following the example below. Note the'step' method:

@RunWith(classOf[JUnitRunner])
class ContaControllerSpec extends MockServices {

    object contaController extends ContaController with MockAtividadeService with MockAccountService with MockPessoaService with MockTelefoneService with MockEmailService{
        pessoaService.update(PessoaFake.id.get, PessoaFake) returns PessoaFake.id.get
    }

    step(Play.start(new FakeAppContext))

    "ContaController [Perfil]" should {

      "atualizar os dados do usuario logado e retornar status '200' (OK)" in {
          val response = contaController.savePerfil()(FakeRequest(POST, "/contas/perfil").withFormUrlEncodedBody(
              ("nome", "nome teste"), ("sobrenome", "sobrenome teste"), ("dataNascimento", "1986-09-12"), ("sexo", "M")).withLoggedIn(config)(uuid))

              status(response) must be equalTo(OK)
        }

        "atualizar os dados do usuario logado enviando o form sem preenchimento e retornar status '400' (BAD_REQUEST)" in {
            val response = contaController.savePerfil()(FakeRequest(POST, "/contas/perfil").withLoggedIn(config)(uuid))
            status(response) must be equalTo(BAD_REQUEST)
        }
    }

    step(Play.stop)
}

The accepted answer did not help me. I am on play 2.2.3 scala 2.10.3. This is what helped me.

May be it could be of some help.

Extend BoneCPPlugin

class NewBoneCPPlugin(val app: play.api.Application) extends BoneCPPlugin(app) {


  override def onStop() {
    //don't stop the BoneCPPlugin
    //plugin.onStop()
  }
}

And in your testspec should be

    class UserControllerSpec extends mutable.Specification with Logging with Mockito {

    val fakeApp = FakeApplication(additionalConfiguration = testDb,withoutPlugins = Seq("play.api.db.BoneCPPlugin"),
                                  additionalPlugins = Seq("NewBoneCPPlugin"))
    "Create action in UserController " should {
            "return 400 status if request body does not contain user json " in new WithApplication(fakeApp) {
        ...
    }
  }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top