Functional testing

Let's look at some of Play's test cases to see how to use the helper methods. For example, consider the DevErrorPageSpec test, which is defined as follows:

object DevErrorPageSpec extends PlaySpecification{

  "devError.scala.html" should {

    val testExceptionSource = new play.api.PlayException.ExceptionSource("test", "making sure the link shows up") {
      ...
    }
    ….
    "show prod error page in prod mode" in {
      val fakeApplication = new FakeApplication() {
        override val mode = play.api.Mode.Prod
      }
      running(fakeApplication) {
        val result = DefaultGlobal.onError(FakeRequest(), testExceptionSource)
        Helpers.contentAsString(result) must contain("Oops, an error occurred")
      }
    }
  }
}

This test starts FakeApplication with the Prod mode and checks the response when FakeRequest encounters an exception.

FakeApplication extends an application and is defined as follows:

case class FakeApplication(config: Map[String, Any] = Map(),
                           path: File = new File("."),
                           sources: Option[SourceMapper] = None,
                           mode: Mode.Mode = Mode.Test,
                           global: GlobalSettings = DefaultGlobal,
                           plugins: Seq[Plugin] = Nil) extends Application {
  val classloader = Thread.currentThread.getContextClassLoader
  lazy val configuration = Configuration.from(config)
}

The method that is running is part of PlayRunners and executes a block of code in the context of a given application. It is defined as follows:

def running[T](app: Application)(block: => T): T = {
    synchronized {
      try {
        Play.start(app)
        block
      } finally {
        Play.stop()
      }
    }
  }

PlayRunners has a few more definitions of how to run, these are:

  • running[T](testServer: TestServer)(block: => T): T: This can be used to execute a block of code in a running server.
  • running[T](testServer: TestServer, webDriver: WebDriver)(block: TestBrowser => T): T: This can be used to execute a block of code in a running server with a test browser.
  • running[T, WEBDRIVER <: WebDriver](testServer: TestServer, webDriver: Class[WEBDRIVER])(block: TestBrowser => T): T: This can also be used to execute a block of code in a running server with a test browser using Selenium WebDriver. This method uses the previous method internally.

Instead of using the running method directly, as an alternative, we could define our tests using the wrapper classes, which make use of the running. There are different helpers for Specs2 and ScalaTest.

Using Specs2

First, let's look at the ones available when using Specs2. They are as follows:

  • WithApplication: It is used to execute a test within the context of a running application. For example, consider a situation where we want to write functional tests for CountController, which is responsible for getting a count of distinct data grouped by a perspective. We can write the test as follows:
    class CountControllerSpec extends PlaySpecification with BeforeExample {
    
      override def before: Any = {
        TestHelper.clearDB
      }
    
      """Counter query""" should {
        """fetch count of visits grouped by browser names""" in new WithApplication {
          TestHelper.postSampleData
    
          val queryString = """applicationId=39&perspective=browser&from=1389949200000&till=1399145400000""".stripMargin
    
          val request = FakeRequest(GET, "/query/count?" + queryString)
          val response = route(request)
          val result = response.get
          status(result) must equalTo(OK)
          contentAsJson(result) must equalTo(TestHelper.browserCount)
        }
      }

    Here, assume that TestHelper is a helper object specifically defined for simplifying the code of test cases (extracting common processes as methods).

    If we need to specify FakeApplication, we can do so by passing it as an argument to the WithApplication constructor:

     val app = FakeApplication()
        """fetch count of visits grouped by browser names""" in new WithApplication(app) {

    This comes in handy when we want to change the default application configurations, GlobalSettings, and so on for the tests.

  • WithServer: It is used to execute tests within the context of a running application on a new TestServer. This is quite useful when we need to start our FakeApplication on a new TestServer at a specific port. After slightly modifying the previous example:
        """fetch count of visits grouped by browser names""" in new WithServer(app = app, port = testPort) {
     {
          ...
     }
  • WithBrowser: It is used to test an application's functionality by performing certain actions in browsers. For example, consider a dummy application where the page title changes on the click of a button. We can test it as follows:
    class AppSpec extends PlaySpecification {
      val app: FakeApplication =
        FakeApplication(
          withRoutes = TestRoute
        )
    
        "run in firefox" in new WithBrowser(webDriver = WebDriverFactory(FIREFOX), app = app) {
         browser.goTo("/testing")
         browser.$("#title").getTexts().get(0) must equalTo("Test Page")
    
         browser.$("b").click()
    
         browser.$("#title").getTexts().get(0) must equalTo("testing")
        }}

    We are assuming TestRoute is a partial function that maps to some of the routes which can then be used in tests.

Using ScalaTest

Now, lets see what ScalaTestPlus-Play, the library with helper methods that are used for testing with the help of ScalaTest, has to offer. In this section, we will see examples from ScalatestPlus-Play wherever applicable. The helpers for ScalaTest are as follows:

  • OneAppPerSuite: It starts FakeApplication using Play.start before running any tests in a suite and then stops it once they are completed. The application is exposed through the variable app and can be overridden if required. From ExampleSpec.scala:
    class ExampleSpec extends PlaySpec with OneAppPerSuite {
    
      // Override app if you need a FakeApplication with other than non-default parameters.
      implicit override lazy val app: FakeApplication =
        FakeApplication(additionalConfiguration = Map("ehcacheplugin" -> "disabled"))
    
      "The OneAppPerSuite trait" must {
        "provide a FakeApplication" in {
          app.configuration.getString("ehcacheplugin") mustBe Some("disabled")
        }
        "make the FakeApplication available implicitly" in {
          def getConfig(key: String)(implicit app: Application) = app.configuration.getString(key)
          getConfig("ehcacheplugin") mustBe Some("disabled")
        }
        "start the FakeApplication" in {
          Play.maybeApplication mustBe Some(app)
        }
      }
    }

    If we wish to use the same application for all or multiple suites, we can define a nested suite. For such an example, we can refer to NestedExampleSpec.scala from the library.

  • OneAppPerTest: It starts a new FakeApplication for each test defined in the suite. The application is exposed through the newAppForTest method and can be overridden if required. For example, consider the OneAppTest test, where each test uses a different FakeApplication obtained through newAppForTest:
    class DiffAppTest extends UnitSpec with OneAppPerTest {
    
      private val colors = Seq("red", "blue", "yellow")
    
      private var colorCode = 0
    
      override def newAppForTest(testData: TestData): FakeApplication = {
        val currentCode = colorCode
        colorCode+=1
        FakeApplication(additionalConfiguration = Map("foo" -> "bar",
          "ehcacheplugin" -> "disabled",
          "color" -> colors(currentCode)
        ))
      }
    
      def getConfig(key: String)(implicit app: Application) = app.configuration.getString(key)
    
      "The OneAppPerTest trait" must {
        "provide a FakeApplication" in {
          app.configuration.getString("color") mustBe Some("red")
        }
        "make another FakeApplication available implicitly" in {
          getConfig("color") mustBe Some("blue")
        }
        "make the third FakeApplication available implicitly" in {
          getConfig("color") mustBe Some("yellow")
        }
      }
    }
  • OneServerPerSuite: It starts a new FakeApplication and a new TestServer for the suite. The application is exposed through the variable app and can be overridden if required. The server's port is set from the variable port and can be changed/modified if required. This has been demonstrated in the example for OneServerPerSuite (ExampleSpec2.scala):
    class ExampleSpec extends PlaySpec with OneServerPerSuite {
    
      // Override app if you need a FakeApplication with other than non-default parameters.
      implicit override lazy val app: FakeApplication =
        FakeApplication(additionalConfiguration = Map("ehcacheplugin" -> "disabled"))
    
      "The OneServerPerSuite trait" must {
        "provide a FakeApplication" in {
          app.configuration.getString("ehcacheplugin") mustBe Some("disabled")
        }
        "make the FakeApplication available implicitly" in {
          def getConfig(key: String)(implicit app: Application) = app.configuration.getString(key)
          getConfig("ehcacheplugin") mustBe Some("disabled")
        }
        "start the FakeApplication" in {
          Play.maybeApplication mustBe Some(app)
        }
        "provide the port number" in {
          port mustBe Helpers.testServerPort
        }
        "provide an actual running server" in {
          import java.net._
          val url = new URL("http://localhost:" + port + "/boum")
          val con = url.openConnection().asInstanceOf[HttpURLConnection]
          try con.getResponseCode mustBe 404
          finally con.disconnect()
        }
      }
    }

    When we require multiple suites to use the same FakeApplication and TestServer, we can define tests using a nested suite similar to NestedExampleSpec2.scala.

  • OneServerPerTest: It starts a new FakeApplication and TestServer for each test defined in the suite. The application is exposed through the newAppForTest method and can be overridden if required. For example, consider the DiffServerTest test, where each test uses a different FakeApplication obtained through newAppForTest and the TestServer port is overridden:
    class DiffServerTest extends PlaySpec with OneServerPerTest {
    
      private val colors = Seq("red", "blue", "yellow")
    
      private var code = 0
    
      override def newAppForTest(testData: TestData): FakeApplication = {
        val currentCode = code
        code += 1
        FakeApplication(additionalConfiguration = Map("foo" -> "bar",
          "ehcacheplugin" -> "disabled",
          "color" -> colors(currentCode)
        ))
      }
    
      override lazy val port = 1234
    
      def getConfig(key: String)(implicit app: Application) = app.configuration.getString(key)
    
      "The OneServerPerTest trait" must {
        "provide a FakeApplication" in {
          app.configuration.getString("color") mustBe Some("red")
        }
        "make another FakeApplication available implicitly" in {
          getConfig("color") mustBe Some("blue")
        }
        "start server at specified port" in {
          port mustBe 1234
        }
      }
    }
  • OneBrowserPerSuite: It provides a new Selenium WebDriver instance per suite. For example, assume that we wish to test the clicking of a button by opening the application in Firefox, the test can be written in the same way as ExampleSpec3.scala:
    @FirefoxBrowser
    class ExampleSpec extends PlaySpec with OneServerPerSuite with OneBrowserPerSuite with FirefoxFactory {
    
      // Override app if you need a FakeApplication with other than non-default parameters.
      implicit override lazy val app: FakeApplication =
        FakeApplication(
          additionalConfiguration = Map("ehcacheplugin" -> "disabled"),
          withRoutes = TestRoute
        )
    
      "The OneBrowserPerSuite trait" must {
        "provide a FakeApplication" in {
          app.configuration.getString("ehcacheplugin") mustBe Some("disabled")
        }
        "make the FakeApplication available implicitly" in {
          def getConfig(key: String)(implicit app: Application) = app.configuration.getString(key)
          getConfig("ehcacheplugin") mustBe Some("disabled")
        }
        "provide a web driver" in {
          go to ("http://localhost:" + port + "/testing")
          pageTitle mustBe "Test Page"
          click on find(name("b")).value
          eventually { pageTitle mustBe "scalatest" }
        }
      }
    }

    We are assuming TestRoute is a partial function that maps to some of the routes, which can then be used in tests.

    The same trait can be used to test the application within multiple browsers, as demonstrated in MultiBrowserExampleSpec.scala. To execute tests in all the browsers, we should use AllBrowsersPerSuite, as follows:

    class AllBrowsersPerSuiteTest extends PlaySpec with OneServerPerSuite with AllBrowsersPerSuite {
    
      // Override newAppForTest if you need a FakeApplication with other than non-default parameters.
      override lazy val app: FakeApplication =
        FakeApplication(
          withRoutes = TestRoute
        )
    
      // Place tests you want run in different browsers in the `sharedTests` method:
      def sharedTests(browser: BrowserInfo) = {
    
          "navigate to testing "+browser.name in {
            go to ("http://localhost:" + port + "/testing")
            pageTitle mustBe "Test Page"
            click on find(name("b")).value
            eventually { pageTitle mustBe "testing" }
          }
    
          "navigate to hello in a new window"+browser.name in {
            go to ("http://localhost:" + port + "/hello")
            pageTitle mustBe "Hello"
            click on find(name("b")).value
            eventually { pageTitle mustBe "helloUser" }
          }
      }
    
      // Place tests you want run just once outside the `sharedTests` method
      // in the constructor, the usual place for tests in a `PlaySpec`
    
      "The test" must {
        "start the FakeApplication" in {
          Play.maybeApplication mustBe Some(app)
        }
      }

    The trait OneBrowserPerSuite can also be used with nested tests. Refer to NestedExampleSpec3.scala.

  • OneBrowserPerTest: It starts a new browser session for each test in the suite. This can be noticed by running the ExampleSpec4.scala test. It's similar to ExampleSpec3.scala, but OneServerPerSuite and OneBrowserPerSuite have been replaced with OneServerPerTest and OneBrowserPerTest, respectively, as shown here:
    @FirefoxBrowser
    class ExampleSpec extends PlaySpec with OneServerPerTest with OneBrowserPerTest with FirefoxFactory {
      ...
    }

    We've also replaced the overridden app variable with the newAppForTest overridden method. Try writing a test that uses the AllBrowsersPerTest trait.

Tip

You can run into an InvalidActorNameException when running multiple functional tests simultaneously on an application, which defines custom actors. We can avoid this by defining a nested test where multiple tests use the same FakeApplication.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.114.85