Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,47 +15,81 @@
*/
package org.scalatestplus.play

import org.scalatest.Args
import org.scalatest.Status
import org.scalatest.TestSuite
import org.scalatest.TestSuiteMixin
import org.scalatest._
import play.api.Application
import play.api.Play

/**
* The base abstract trait for one app per suite.
*/
trait BaseOneAppPerSuite extends TestSuiteMixin { this: TestSuite with FakeApplicationFactory =>
trait BaseOneAppPerSuite extends SuiteMixin with AppProvider with BeforeAndAfterAll with BeforeAndAfterEachTestData {
this: Suite with FakeApplicationFactory =>

@volatile private var privateApp: Application = _

/**
* An implicit instance of `Application`.
*/
implicit lazy val app: Application = fakeApplication()
final implicit def app: Application = {
require(privateApp != null, "Test isn't running yet so application is not available")
privateApp
}

protected override def beforeAll: Unit = {
privateApp = fakeApplication()
Play.start(app)
super.beforeAll()
}

protected override def afterAll(): Unit = {
try {
super.afterAll()
} finally {
val theApp = app
privateApp = null
Play.stop(theApp)
}
}

/**
* Places a reference to the app into per-test instances
*/
protected override def beforeEach(testData: TestData): Unit = {
if (isInstanceOf[OneInstancePerTest])
setApplicationFrom(testData.configMap)
super.beforeEach(testData)
}

private def setApplicationFrom(configMap: ConfigMap): Unit = {
synchronized { privateApp = providerFrom(configMap).app }
}

/**
* Invokes `Play.start`, passing in the `Application` provided by `app`, and places
* that same `Application` into the `ConfigMap` under the key `org.scalatestplus.play.app` to make it available
* to nested suites; calls `super.run`; and lastly ensures `Play.stop` is invoked after all tests and nested suites have completed.
*
* @param testName an optional name of one test to run. If `None`, all relevant tests should be run.
* I.e., `None` acts like a wildcard that means run all relevant tests in this `Suite`.
* @param args the `Args` for this run
* @return a `Status` object that indicates when all tests and nested suites started by this method have completed, and whether or not a failure occurred.
* Places the app into the test's ConfigMap
*/
abstract override def testDataFor(testName: String, configMap: ConfigMap): TestData = {
super.testDataFor(testName, configMap + ("org.scalatestplus.play.app" -> providerFrom(configMap).app))
}

private def providerFrom(configMap: ConfigMap): AppProvider = {
configMap
.getOptional[AppProvider]("org.scalatestplus.play.app.provider")
.getOrElse(
throw new IllegalArgumentException(
"BaseOneAppPerSuite needs an Application value associated with key \"org.scalatestplus.play.app.provider\" in the config map"
)
)
}

//put a provider into the config map(instead of app directly), so that if tests are excluded, the app is never created
abstract override def run(testName: Option[String], args: Args): Status = {
Play.start(app)
try {
val newConfigMap = args.configMap + ("org.scalatestplus.play.app" -> app)
// if a test is running under OneInstancePerTest, the config map will already be set
if (args.runTestInNewInstance) super.run(testName, args)
else {
val newConfigMap = args.configMap + ("org.scalatestplus.play.app.provider" -> this)
val newArgs = args.copy(configMap = newConfigMap)
val status = super.run(testName, newArgs)
status.whenCompleted { _ =>
Play.stop(app)
}
status
} catch { // In case the suite aborts, ensure the app is stopped
case ex: Throwable =>
Play.stop(app)
throw ex
super.run(testName, newArgs)
}
}

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package org.scalatestplus.play

import org.scalatest.TestData
import org.scalatest.TestSuite
import org.scalatest.TestSuiteMixin
import org.scalatest._
import play.api.Application
import play.api.Play
import play.api.test.Helpers

/**
* Trait that provides a new `Application` instance for each test.
*
* This `TestSuiteMixin` trait's overridden `withFixture` method creates a new `Application`
* This `SuiteMixin` trait's overridden `withFixture` method creates a new `Application`
* before each test and ensures it is cleaned up after the test has completed. You can
* access the `Application` from your tests as method `app` (which is marked implicit).
*
Expand Down Expand Up @@ -45,33 +44,34 @@ import play.api.test.Helpers
* }
* </pre>
*/
trait BaseOneAppPerTest extends TestSuiteMixin with AppProvider { this: TestSuite with FakeApplicationFactory =>
trait BaseOneAppPerTest extends SuiteMixin with BeforeAndAfterEachTestData with AppProvider {
this: Suite with FakeApplicationFactory =>

private var appPerTest: Application = _

/**
* Creates new instance of `Application` with parameters set to their defaults. Override this method if you
* need a `Application` created with non-default parameter values.
*/
def newAppForTest(testData: TestData): Application = fakeApplication()

private var appPerTest: Application = _

/**
* Implicit method that returns the `Application` instance for the current test.
*/
final implicit def app: Application = synchronized { appPerTest }

/**
* Creates a new `Application` instance before executing each test, and
* ensure it is cleaned up after the test completes. You can access the `Application` from
* your tests via `app`.
*
* @param test the no-arg test function to run with a fixture
* @return the `Outcome` of the test execution
*/
abstract override def withFixture(test: NoArgTest) = {
synchronized { appPerTest = newAppForTest(test) }
Helpers.running(app) {
super.withFixture(test)
protected override def beforeEach(td: TestData): Unit = {
synchronized { appPerTest = newAppForTest(td) }
Play.start(appPerTest)
super.beforeEach(td)
}

protected override def afterEach(td: TestData): Unit = {
try {
super.afterEach(td)
} finally {
Play.stop(appPerTest)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import play.api.test._
* `Application` with non-default parameters, override `app`. If it needs a different port number,
* override `port`.
*
* This `TestSuiteMixin` trait's overridden `run` method calls `start` on the `TestServer`
* This `SuiteMixin` trait's overridden `run` method calls `start` on the `TestServer`
* before executing the `Suite` via a call to `super.run`.
* In addition, it places a reference to the `Application` provided by `app` into the `ConfigMap`
* under the key `org.scalatestplus.play.app` and to the port number provided by `port` under the key
Expand Down Expand Up @@ -134,41 +134,85 @@ import play.api.test._
* }
* </pre>
*/
trait BaseOneServerPerSuite extends TestSuiteMixin with ServerProvider { this: TestSuite with FakeApplicationFactory =>
trait BaseOneServerPerSuite
extends SuiteMixin
with BeforeAndAfterAll
with BeforeAndAfterEachTestData
with ServerProvider {
this: Suite with FakeApplicationFactory =>

@volatile private var privateServer: RunningServer = _

final implicit def runningServer: RunningServer = {
require(privateServer != null, "Test isn't running yet so the server endpoints are not available")
privateServer
}

/**
* An implicit instance of `Application`.
*/
implicit lazy val app: Application = fakeApplication()
final implicit def app: Application = {
runningServer.app
}

protected override def beforeAll(): Unit = {
privateServer = startTestServer
super.beforeAll()
}

protected def startTestServer: RunningServer = DefaultTestServerFactory.start(fakeApplication())

protected override def afterAll(): Unit = {
try {
super.afterAll()
} finally {
val server = runningServer
privateServer = null
server.stopServer.close()
}
}

/**
* Places a reference to the server into per-test instances
*/
protected override def beforeEach(testData: TestData): Unit = {
super.beforeEach(testData)
if (isInstanceOf[OneInstancePerTest])
setServerFrom(testData.configMap)
}

protected implicit lazy val runningServer: RunningServer =
DefaultTestServerFactory.start(app)
private def setServerFrom(configMap: ConfigMap): Unit = {
synchronized { privateServer = providerFrom(configMap).runningServer }
}

private def providerFrom(configMap: ConfigMap): ServerProvider = {
configMap
.getOptional[ServerProvider]("org.scalatestplus.play.server.provider")
.getOrElse(
throw new IllegalArgumentException(
"BaseOneServerPerSuite needs an Application value associated with key \"org.scalatestplus.play.server.provider\" in the config map"
)
)
}

/**
* Invokes `start` on a new `TestServer` created with the `Application` provided by `app` and the
* port number defined by `port`, places the `Application` and port number into the `ConfigMap` under the keys
* `org.scalatestplus.play.app` and `org.scalatestplus.play.port`, respectively, to make
* them available to nested suites; calls `super.run`; and lastly ensures the `Application` and test server are stopped after
* all tests and nested suites have completed.
*
* @param testName an optional name of one test to run. If `None`, all relevant tests should be run.
* I.e., `None` acts like a wildcard that means run all relevant tests in this `Suite`.
* @param args the `Args` for this run
* @return a `Status` object that indicates when all tests and nested suites started by this method have completed, and whether or not a failure occurred.
* Places the server port into the test's ConfigMap
*/
abstract override def testDataFor(testName: String, configMap: ConfigMap): TestData = {
val serverProvider = providerFrom(configMap)
val newConfigMap = configMap + ("org.scalatestplus.play.app" -> serverProvider.app) + ("org.scalatestplus.play.port" -> serverProvider.port)
super.testDataFor(testName, newConfigMap)
}

//put a provider into the config map(instead of server directly), so that if tests are excluded, the server is never created
abstract override def run(testName: Option[String], args: Args): Status = {
try {
val newConfigMap = args.configMap + ("org.scalatestplus.play.app" -> app) + ("org.scalatestplus.play.port" -> port)
// if a test is running under OneInstancePerTest, the config map will already be set
if (args.runTestInNewInstance) super.run(testName, args)
else {
val newConfigMap = args.configMap + ("org.scalatestplus.play.server.provider" -> this)
val newArgs = args.copy(configMap = newConfigMap)
val status = super.run(testName, newArgs)
status.whenCompleted { _ =>
runningServer.stopServer.close()
}
status
} catch { // In case the suite aborts, ensure the server is stopped
case ex: Throwable =>
runningServer.stopServer.close()
throw ex
super.run(testName, newArgs)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.scalatest._
/**
* Trait that provides a new `Application` and running `TestServer` instance for each test executed in a ScalaTest `Suite`.
*
* This `TestSuiteMixin` trait overrides ScalaTest's `withFixture` method to create a new `Application` and `TestServer`
* This `SuiteMixin` trait overrides ScalaTest's `withFixture` method to create a new `Application` and `TestServer`
* before each test, and ensure they are cleaned up after the test has completed. The `Application` is available (implicitly) from
* method `app`. The `TestServer`'s port number is available as `port` (and implicitly available as `portNumber`, wrapped
* in a [[org.scalatestplus.play.PortNumber PortNumber]]).
Expand Down Expand Up @@ -73,7 +73,8 @@ import org.scalatest._
* }
* </pre>
*/
trait BaseOneServerPerTest extends TestSuiteMixin with ServerProvider { this: TestSuite with FakeApplicationFactory =>
trait BaseOneServerPerTest extends SuiteMixin with BeforeAndAfterEachTestData with ServerProvider {
this: Suite with FakeApplicationFactory =>

@volatile private var privateApp: Application = _
@volatile private var privateServer: RunningServer = _
Expand Down Expand Up @@ -108,28 +109,23 @@ trait BaseOneServerPerTest extends TestSuiteMixin with ServerProvider { this: Te
protected def newServerForTest(app: Application, testData: TestData): RunningServer =
DefaultTestServerFactory.start(app)

/**
* Creates new `Application` and running `TestServer` instances before executing each test, and
* ensures they are cleaned up after the test completes. You can access the `Application` from
* your tests as `app` and the `TestServer`'s port number as `port`.
*
* @param test the no-arg test function to run with a fixture
* @return the `Outcome` of the test execution
*/
abstract override def withFixture(test: NoArgTest) = {
// Need to synchronize within a suite because we store current app/server in fields in the class
// Could possibly pass app/server info in a ScalaTest object?
protected override def beforeEach(td: TestData): Unit = {
lock.synchronized {
privateApp = newAppForTest(test)
privateServer = newServerForTest(app, test)
try super.withFixture(test)
finally {
val rs = privateServer // Store before nulling fields
privateApp = null
privateServer = null
// Stop server and release locks
rs.stopServer.close()
}
privateApp = newAppForTest(td)
privateServer = newServerForTest(app, td)
}
super.beforeEach(td)
}

protected override def afterEach(td: TestData): Unit = {
try {
super.afterEach(td)
} finally {
val rs = privateServer // Store before nulling fields
privateApp = null
privateServer = null
// Stop server and release locks
rs.stopServer.close()
}
}
}
Loading