scalatestplus-play icon indicating copy to clipboard operation
scalatestplus-play copied to clipboard

Use GuiceOneAppPerSuite with async tests

Open dpoetzsch opened this issue 6 years ago • 5 comments

I have a async test suite that extends AsyncFunSpec.

Now, it seems to be not possible to also mix-in GuiceOneAppPerSuite:

// does not work
class PostSpec extends AsyncFunSpec with GuiceOneAppPerSuite with Matchers {
  it("returns posts") {
    val repo = app.injector.instanceOf[PostRepository]

    repo.create("my post").map { _ =>
      val request = FakeRequest(GET, "/posts")
      val result = route(app, request).get

      status(result) shouldEqual OK
      contentAsJson(result).as[JsArray].value.size shouldEqual 1
    }
  }
}

I am no scala magician, but I figure this is because GuiceOneAppPerSuite demands TestSuite while AsyncFunSpec inherits from AsyncTestSuite.

I figure an easy workaround would be to have an (otherwise identical) GuiceOneAppPerSuiteAsync that uses the AsyncTestSuite inheritance tree as follows:

/*
 * Copyright 2001-2016 Artima, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// this is an adjusted version of
// https://github.com/playframework/scalatestplus-play/blob/master/module/src/main/scala/org/scalatestplus/play/BaseOneAppPerSuite.scala
package org.scalatestplus.play

import org.scalatest.{Args, AsyncTestSuite, AsyncTestSuiteMixin, Status}
import org.scalatestplus.play.FakeApplicationFactory
import play.api.{Application, Play}

/**
  * The base abstract trait for one app per suite.
  */
trait BaseOneAppPerSuiteAsync extends AsyncTestSuiteMixin { this: AsyncTestSuite with FakeApplicationFactory =>

  /**
    * An implicit instance of `Application`.
    */
  implicit lazy val app: Application = fakeApplication()

  /**
    * 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.
    */
  abstract override def run(testName: Option[String], args: Args): Status = {
    Play.start(app)
    try {
      val newConfigMap = args.configMap + ("org.scalatestplus.play.app" -> app)
      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
    }
  }
}

trait GuiceOneAppPerSuiteAsync extends scala.AnyRef with BaseOneAppPerSuiteAsync with org.scalatestplus.play.guice.GuiceFakeApplicationFactory {
  this : GuiceOneAppPerSuiteAsync with org.scalatest.AsyncTestSuite =>
}

Is there an easier way? And if not, should I create a pull request to add these traits?

dpoetzsch avatar May 15 '18 15:05 dpoetzsch

Looking to do the same. Any suggestions?

MLG-ANolan avatar Aug 13 '18 18:08 MLG-ANolan

Hi @dpoetzsch,

Thanks for investigating this. Another solution is to declare GuiceOneAppPerSuite with a self-type for Suite instead of TestSuite (Suite is the parent type for both TestSuite and AsyncTestSuite):

trait GuiceOneAppPerSuite extends BaseOneAppPerSuite with GuiceFakeApplicationFactory {
  this: org.scalatest.Suite =>
}

That way, it should work when you mix GuiceOneAppPerSuite and AsyncTestSuite or TestSuite. If you want to give it a try and submit PR, let me know so I can help you by reviewing it. :-)

Best.

marcospereira avatar Aug 23 '18 18:08 marcospereira

For those looking for a quick hack, you can declare the async suite as a nested suite:

import org.scalatest._
import org.scalatestplus.play.guice.GuiceOneAppPerSuite

import scala.collection.immutable

class GuiceOneAppPerAsyncSuite extends TestSuite with GuiceOneAppPerSuite {

  val nestedSuite = new AsyncFlatSpec {
    // async tests here
  }

  override def nestedSuites: immutable.IndexedSeq[Suite] = Vector(nestedSuite)
}

tnielens avatar Mar 11 '19 08:03 tnielens

@marcospereira I'm also no Scala magician, but I was trying out your suggestion above to attempt this:

trait GuiceOneAppPerSuite extends BaseOneAppPerSuite with GuiceFakeApplicationFactory {
  this: org.scalatest.Suite =>
}

It seems like that won't work because BaseOneAppPerSuite extends TestSuiteMixin. Does this mean you're left with rewriting a bunch of the tree with async versions like @dpoetzsch's original suggestion?

wuservices avatar Sep 30 '19 07:09 wuservices

took at crack at this, moved TestSuite/TestSuiteMixin to Suite/SuiteMixin: https://github.com/playframework/scalatestplus-play/pull/255

kwinter avatar Apr 25 '20 16:04 kwinter