15

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
    }
  }
wfbarksdale
  • 6,962
  • 11
  • 60
  • 87

7 Answers7

15

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")
  }
Rajish
  • 6,525
  • 3
  • 31
  • 49
  • Note that instead of writing: "class SomeSpec extends Specification { override def is = args(sequential = true) ^ super.is", you can write: "class SomeSpec extends Specification { sequential" – Eric Aug 21 '12 at 07:36
  • @Eric Thanks! I didn't find that in the documentation. Is it a recent feature? – Rajish Aug 21 '12 at 08:44
  • Actually I still seem to be having the problem, I posted a stack trace here http://stackoverflow.com/questions/12170009/still-cant-run-multiple-specs2-tests-against-play-fakeapp-with-mongodb – wfbarksdale Aug 29 '12 at 02:10
3

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

Ph. Wiget
  • 101
  • 1
  • 4
1

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())
  }
}
Leon Radley
  • 7,081
  • 5
  • 31
  • 49
  • It was very helpful! I've used even simpler "running(TestServer(3333, FakeApplication()))" – ozma Mar 31 '15 at 11:06
0

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

Andy Petrella
  • 4,325
  • 24
  • 29
  • Unfortunately I am using the mongodb, So I don't believe I can use the in memory database. – wfbarksdale Aug 19 '12 at 20:25
  • I hand rolled my own Mongo plugin a while back, and have a test-specific subclass that only connects to the database when the first test starts and does not disconnect when that test ends. Not ideal, but it did the job. – Alex Varju Aug 19 '12 at 22:03
  • To achieve such goal with specs2 you just have use Step. One at the start of the Fragments that starts Mongo and another at then end that stops it. You should used them in a trait defining the 'is' method to use them around your tests. – Andy Petrella Aug 20 '12 at 04:14
  • look for Template in this page, whis is exactly explaining what to do http://etorreborre.github.com/specs2/guide/org.specs2.guide.Structure.html – Andy Petrella Aug 20 '12 at 04:47
0

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
            }
        }
    }
    ....
buster84
  • 232
  • 2
  • 9
0

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)
}
DontVoteMeDown
  • 19,660
  • 10
  • 65
  • 96
0

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) {
        ...
    }
  }
}
user809564
  • 249
  • 1
  • 3
  • 10