Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to access application components in functional tests while using compile time DI? #8099

Closed
Dasiu opened this issue Dec 16, 2017 · 1 comment

Comments

@Dasiu
Copy link

Dasiu commented Dec 16, 2017

API: Scala, Play: 2.6.X

My need is to write functional test, which refers to application component with compile time DI and with server and whole application running. After reading docs, I'm not sure what is proper way to achieve that. I've learned, that I should use injector in order to access application components (including business components) like this:

class ExampleSpec extends PlaySpec with GuiceOneServerPerSuite {

  // Override app if you need an Application with other than
  // default parameters.
  override def fakeApplication(): Application =
    new GuiceApplicationBuilder().disable[EhCacheModule].router(Router.from {
      case GET(p"/") => Action { Ok("ok") }
    }).build()

  "test server logic" in {
    val wsClient = app.injector.instanceOf[WSClient]
    val myPublicAddress = s"localhost:$port"
    val testPaymentGatewayURL = s"http://$myPublicAddress"
    // The test payment gateway requires a callback to this server before it returns a result...
    val callbackURL = s"http://$myPublicAddress/callback"
    // await is from play.api.test.FutureAwaits
    val response = await(wsClient.url(testPaymentGatewayURL).withQueryString("callbackURL" -> callbackURL).get())

    response.status mustBe OK
  }
}

https://www.playframework.com/documentation/2.6.x/ScalaFunctionalTestingWithScalaTest

Intuitively, I presumed, that for compile time DI it can be done in the same way, of course, without additional magic, programmer needs to set up such injector himself - explicitly. My attempt to do that failed, because BuiltInComponents.injector is lazy and following line do not compile:

override val injector = new SimpleInjector(super.injector) + ??? /* additional components */

with error: "super may not be used on lazy value injector". Call to super is needed, since BuiltInComponents contains some basic dependencies, which are probably needed by an application.

After facing that I fell back to another solution. Underneath playframework's test utilities initializes app variable for each test. The variable is initialized based on WithApplicationComponents.components definition. components def is also available in test's body, so my idea was to call it in order to access components required by test, like here:

"dummy test" in {
  components.myDummyComponent
}

it actually works but it looks like fragile thing, because components is a definition, so each call creates different BuiltInComponentsFromContext - different to create application for test and different to access application component.

Given all that, my questions are:

  • Shouldn't first solution - with injector and compile time DI - be possible to implement? Maybe it is but I failed to discover that and documentation is not clear about it.
  • Shouldn't concrete instance from BuiltInComponentsFromContext be confined to each test in similar fashion like with aforementioned app variable? For me each call to components is like creating another test box and I want to have only one.
  • In general, what is current way to access application components in functional tests (with server and whole application running)?

My current test infrastructure is a little bit more complicated, it can be found here, if needed: https://github.com/Dasiu/realworld-starter-kit/tree/master/test

@gmethvin
Copy link
Member

gmethvin commented Dec 18, 2017

@Dasiu It seems like you're actually talking about https://github.com/playframework/scalatestplus-play, rather than playframework itself. The core issue I believe is that you want something like components to access at the class level, but you want to make sure that you're using the same exact instance used by the running application.

The first solution you mentioned seems like an ugly hack. If you're using compile-time DI, ideally you want to access the components in a type-safe way. The best way to do that is directly using the actual components class you used to create the application. Yes, it would be possible to implement an injector that inspects the components class at runtime and calls the right method, but there's no good reason to add another level of indirection.

As far as I can tell this is not a bug in Play at all so I'm closing this, but I opened playframework/scalatestplus-play#108 in scalatestplus-play that I think explains the main problem you're describing. That also describes an idempotent way to access the components for the running application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants