Play 2.6 Testing controller error ControllerComponents not set! Call setControllerComponents or create the instance with dependency injection.

1,989 views
Skip to first unread message

Tianhao Li

unread,
Aug 1, 2017, 8:57:58 AM8/1/17
to Play Framework
I am trying to migrate my Play application from 2.5 to 2.6 and so far the code is OK and compiles but got some problems for my controller testing.

Below is my controller and it's test:

class MyController @Inject()(myService: MyService)(implicit ec: ExecutionContext) extends InjectedController {


}


class MyControllerSpec extends FunSpec with TypeCheckedTripleEquals with Results with ScalaFutures with MockitoSugar with BeforeAndAfter with GuiceOneAppPerSuite {


 
implicit lazy val materializer: Materializer = app.materializer


 
var mockMyService: MyService = _


 
var controller: MyController = _


  before
{
    mockMyService
= mock[MyService]


    controller
= new MyController(mockMyService)
 
}
}




However I got the errors when I run the tests:

java.util.NoSuchElementException: ControllerComponents not set! Call setControllerComponents or create the instance with dependency injection.


Will Sargent

unread,
Aug 1, 2017, 12:49:43 PM8/1/17
to play-fr...@googlegroups.com
You need to set the controller components, which is usually done through the constructor:


In your case, you're using InjectedController, so you may want to make an anonymous subclass that overrides the controllerComponents field with the stubs.

--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/059986cb-1d45-4292-9e9c-6a898739dc3b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Tudor Mazilu

unread,
Aug 31, 2017, 2:53:00 AM8/31/17
to Play Framework
Hi Will, Tianhao,

Is there no other way to do this? Somehow to bind them with Guice, looking like
> bind[ControllerComponents].toInstance(Helpers.stubControllerComponents()) along with the other components?
in a Guice module?

It's a bit of a nuisance to do this for every controller in my app. 

In every test class, I would need to define
private def controller = {
 
new MyController() {
 
override def controllerComponents: ControllerComponents = Helpers.stubControllerComponents()
 
}
}


On Tuesday, 1 August 2017 19:49:43 UTC+3, Will Sargent wrote:
You need to set the controller components, which is usually done through the constructor:


In your case, you're using InjectedController, so you may want to make an anonymous subclass that overrides the controllerComponents field with the stubs.
On Tue, Aug 1, 2017 at 5:57 AM, Tianhao Li <ysi...@gmail.com> wrote:
I am trying to migrate my Play application from 2.5 to 2.6 and so far the code is OK and compiles but got some problems for my controller testing.

Below is my controller and it's test:

class MyController @Inject()(myService: MyService)(implicit ec: ExecutionContext) extends InjectedController {


}


class MyControllerSpec extends FunSpec with TypeCheckedTripleEquals with Results with ScalaFutures with MockitoSugar with BeforeAndAfter with GuiceOneAppPerSuite {


 
implicit lazy val materializer: Materializer = app.materializer


 
var mockMyService: MyService = _


 
var controller: MyController = _


  before
{
    mockMyService
= mock[MyService]


    controller
= new MyController(mockMyService)
 
}
}




However I got the errors when I run the tests:

java.util.NoSuchElementException: ControllerComponents not set! Call setControllerComponents or create the instance with dependency injection.


--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.

Greg Methvin

unread,
Aug 31, 2017, 3:35:16 AM8/31/17
to play-framework
On Wed, Aug 30, 2017 at 7:08 AM, Tudor Mazilu <tudor....@pure360.com> wrote:
Hi Will, Tianhao,

Is there no other way to do this? Somehow to bind them with Guice, looking like
> bind[ControllerComponents].toInstance(Helpers.stubControllerComponents()) along with the other components?
in a Guice module?


Yes, if you are using Guice in your tests then you can do that.
 
It's a bit of a nuisance to do this for every controller in my app. 

In every test class, I would need to define
private def controller = {
 
new MyController() {
 
override def controllerComponents: ControllerComponents = Helpers.stubControllerComponents()
 
}
}


If you have a running app with an injector, you can use app.injector.instanceOf[MyController]. You can override the binding in your GuiceApplicationBuilder.

If you have a unit test (with no app) using InjectedController, you can write a helper for setting the components that works for all InjectedControllers:

def stubify[C <: InjectedController](controller: C): C =
  controller.setControllerComponents(Helpers.stubControllerComponents())
// stubify(new MyController(...))

You can also create your own subtrait of InjectedController that overrides fallbackControllerComponents.

If you're planning on writing a lot of tests with mocks and stub values, I would recommend extending either BaseController or AbstractController and passing the components in the constructor. 


On Tuesday, 1 August 2017 19:49:43 UTC+3, Will Sargent wrote:
You need to set the controller components, which is usually done through the constructor:


In your case, you're using InjectedController, so you may want to make an anonymous subclass that overrides the controllerComponents field with the stubs.

On Tue, Aug 1, 2017 at 5:57 AM, Tianhao Li <ysi...@gmail.com> wrote:
I am trying to migrate my Play application from 2.5 to 2.6 and so far the code is OK and compiles but got some problems for my controller testing.

Below is my controller and it's test:

class MyController @Inject()(myService: MyService)(implicit ec: ExecutionContext) extends InjectedController {


}


class MyControllerSpec extends FunSpec with TypeCheckedTripleEquals with Results with ScalaFutures with MockitoSugar with BeforeAndAfter with GuiceOneAppPerSuite {


 
implicit lazy val materializer: Materializer = app.materializer


 
var mockMyService: MyService = _


 
var controller: MyController = _


  before
{
    mockMyService
= mock[MyService]


    controller
= new MyController(mockMyService)
 
}
}




However I got the errors when I run the tests:

java.util.NoSuchElementException: ControllerComponents not set! Call setControllerComponents or create the instance with dependency injection.


--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/059986cb-1d45-4292-9e9c-6a898739dc3b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/e42afd7a-dc43-4a64-b2aa-c8f300daae25%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Greg Methvin
Tech Lead - Play Framework

Tudor Mazilu

unread,
Aug 31, 2017, 7:30:14 AM8/31/17
to Play Framework
Thanks for your reply, Greg. But I have some further questions.

We are using Guice to create the app for both production and tests. 

This is how I 
class MyApplicationBuilder(dbSuffix: LiveDBID = nextDbId) {

 
def build(): Application = {
    val env
= Environment.simple()
    val context
= ApplicationLoader.Context(
      environment
= env,
      sourceMapper
= None,
      webCommands
= new DefaultWebCommands(),
      initialConfiguration
= Configuration.load(env),
      lifecycle
= new DefaultApplicationLifecycle()
   
)

    val myEnv
= new MockEnvironment

    val app
= new MyApplicationLoader()
     
.builder(context)
     
.overrides(new OverridingEnvironmentModule(myEnv))
     
.bindings(bind[LiveDBID].toInstance(dbSuffix))
     
.build()

    val lifeCycle
= app.injector.instanceOf[play.api.inject.ApplicationLifecycle]
   
new ApplicationStopper(lifeCycle, myEnv, dbSuffix)
    app
 
}
}

class MyApplicationLoader extends GuiceApplicationLoader {
 
override def builder(context: ApplicationLoader.Context): GuiceApplicationBuilder = {
    val conf
= context.initialConfiguration
    initialBuilder
     
.in(context.environment)
     
.loadConfig(conf)
     
.overrides(overrides(context): _*)
     
.bindings(
       
ModuleMock
     
)
 
}
}

object ModuleMock extends Module {
 
def bindings(environment: Environment, configuration: Configuration) = {
   
Seq(
      bind
[Lang].toInstance(Lang.defaultLang),
      bind
[ControllerComponents].toInstance(Helpers.stubControllerComponents()),
     
...
   
)
 
}
}

In the test class, we have

private val app = new MyApplicationBuilder(dbSuffix).build()
private val mockEnv = app.injector.instanceOf(classOf[MockEnvironment])

private def controller(service: AuthorizationService) = {
 
new MyController(
   mockEnv
,
   service
,
   app
.injector.instanceOf(classOf[StringResources]),
   app
.injector.instanceOf(classOf[MessagesApi]),
   app
.injector.instanceOf(classOf[Lang])
 
)
}

If I try to run it, I get the following error: 
[info]   com.google.inject.CreationException: Unable to create injector, see the following errors:
[info]
[info] 1) A binding to play.api.mvc.ControllerComponents was already configured at com.dummy.ModuleMock$.bindings(TestUtil.scala:58):
[info] Binding(interface play.api.mvc.ControllerComponents to ProviderTarget(play.api.inject.BindingKey$$anon$1@77bec56c)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1).
[info]   at play.api.inject.BuiltinModule$$anonfun$$lessinit$greater$1.apply(BuiltinModule.scala:59):
[info] Binding(interface play.api.mvc.ControllerComponents to ConstructionTarget(class play.api.mvc.DefaultControllerComponents)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
If I remove the binding from my ModuleMock and create the following:
private class OverridingControllerComponents extends AbstractModule with ScalaModule {
 
override def configure(): Unit = {
   bind
[ControllerComponents].toInstance(Helpers.stubControllerComponents())
 
}
}

And the MyApplicationBuilder is changed to call the OverridingControllerComoments class:
val app = new PureApplicationLoader()
 
.builder(context)
 
.overrides(new OverridingEnvironmentModule(pureEnv))
 
.overrides(new OverridingControllerComponents)
 
.bindings(bind[LiveDBID].toInstance(dbSuffix))
 
.build()

All of a sudden, the error is changed to 
[info]   java.util.NoSuchElementException: ControllerComponents not set! Call setControllerComponents or create the instance with dependency injection.
[info]   at play.api.mvc.InjectedController$class.fallbackControllerComponents(Controller.scala:195)
[info]   at com.dummy.RestBaseController.fallbackControllerComponents(RestBaseController.scala:16)
[info]   at play.api.mvc.InjectedController$class.controllerComponents(Controller.scala:180)
[info]   at com.dummy.restbase.RestBaseController.controllerComponents(RestBaseController.scala:16)
[info]   at play.api.mvc.BaseController$class.Action(Controller.scala:161)
[info]   at com.dummy.restbase.RestBaseController.Action(RestBaseController.scala:16)
[info]   at com.dummy.MyController.authorizationCode(Authorization.scala:34)
[info]   at com.dummy.MyControllerTest$$anonfun$1$$anonfun$apply$mcV$sp$1.apply(MyControllerTest.scala:52)
[info]   at com.dummy.MyControllerTest$$anonfun$1$$anonfun$apply$mcV$sp$1.apply(MyControllerTest.scala:50)
[info]   at org.scalatest.OutcomeOf$class.outcomeOf(OutcomeOf.scala:85)

It is as if, it doesn't see that injected ControllerComponents which I find very odd.

Creating an anonymous instance of MyController class with overriding def controllerComponents does work but it's sort of a pain.

Greg Methvin

unread,
Aug 31, 2017, 8:21:46 AM8/31/17
to play-framework
InjectedController relies on method injection. The setControllerComponents method must be called on the controller to set the components. That won't happen if you manually create an instance using the constructor, since Guice has no control over the object in that case. If you used injector.instanceOf[MyController], Guice would inject the method for you, since it's actually creating the instance and has the opportunity to see if there are any methods or fields to inject. 

You're also using two different methods for overrides: Guice overrides and manually passing arguments to the constructor. Ideally you would do either one or the other. If you overrode the AuthorizationService in a Guice module instead of passing it directly, you would be able to ask the injector for a MyController directly and have everything injected.

If you create the controller manually, you will have to call setControllerComponents after to set up the state. Or like I said before you can avoid InjectedController and method injection altogether and pass a ControllerComponents to the constructor. This is the way usually shown in the Play documentation. InjectedController is most useful if you plan to write most or all of your tests with Guice, and you care a lot about saving an extra constructor parameter.

--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Tudor Mazilu

unread,
Aug 31, 2017, 9:20:38 AM8/31/17
to Play Framework
Thank you very much. I went with 
extends AbstractController(controllerComponents)
instead of InjectedController, so, the last solution that you proposed. Everything is looking much better and the tests pass as expected.

Kind regards,
Tudor Mazilu
Reply all
Reply to author
Forward
0 new messages