How to write a unit test for a custom Play 2.6 Action

1,100 views
Skip to first unread message

Yang Wong

unread,
Jul 10, 2017, 3:50:18 PM7/10/17
to Play Framework
I'm having trouble unit testing a controller in Play 2.6 and would appreciate the help.

The problem is that my Controller method uses a custom Action 'languageAction' and I get a null pointer exception when I run the tests

My Controller method:

class ApplicationController @Inject()(languageAction: LanguageAction, cc: ControllerComponents)
  extends AbstractController(cc)
    with I18nSupport
    with LazyLogging {

  def error(error: String): Action[AnyContent] = languageAction {
    implicit request: RequestHeader =>
      BadRequest(views.html.error(error))
  }

}

My custom action that looks at the query string before it is processed by the controller and adds a header to the request:

class LanguageAction @Inject() (parser: BodyParsers.Default)(implicit ec: ExecutionContext) extends ActionBuilderImpl(parser) {
  override def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]): Future[Result] = {
    val languageCode = request.getQueryString("ui_locales") match {
      case Some("cy") => "cy"
      case _ => "en"
    }
    val newRequest = request.withHeaders(Headers(("Accept-Language", languageCode)))
    block(newRequest)
  }
}

My unit test, I am mocking 'languageAction' and I suspect Mockito doesn't mock it properly. I get a null pointer exception on line 16.


class ApplicationControllerSpec extends PlaySpec
with MockitoSugar with ScalaFutures with GuiceOneAppPerTest with Results {

implicit lazy val materializer: Materializer = app.materializer
val languageAction = mock[LanguageAction]

"error"
should {
"return a 400 bad request" in {

val controller = new ApplicationController(languageAction, stubControllerComponents())

val newRequest = FakeRequest().withCSRFToken

val method = controller.error("error")(newRequest) //line 16

status(method) mustEqual 400
}
}
}

I've tried app.injecting in an instance of 'languageAction' but that didn't work.I've also tried mocking out 'languageAction.invokeBlock' as it's a future but that also didn't work. Any advice or solutions would be welcome, thanks.

Justin du coeur

unread,
Jul 10, 2017, 5:02:57 PM7/10/17
to play-fr...@googlegroups.com
Hmm.  I don't know Mockito all that well (first started using it last week myself), but I think you're expecting too much of it here.  Do you have any reason to believe it can cope with the injected constructor parameters of LanguageAction?

I think it might work if you add a when(languageAction.invokeBlock(... stuff...)).thenReturn() clause -- that would make Mockito intercept the call to invokeBlock(), instead of (I suspect) passing it through to a half-constructed object and crashing...

--
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/777841f8-2af9-441c-8356-b6f1a4098bbf%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Yang Wong

unread,
Jul 11, 2017, 6:59:37 AM7/11/17
to Play Framework
Thanks for the reply, I've tried writing a when/thenReturn but I still get the same error:

when(languageAction.invokeBlock(Matchers.any(),Matchers.any()))
  .thenReturn(Future(Result(ResponseHeader(200),HttpEntity.NoEntity)))
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.

Justin du coeur

unread,
Jul 11, 2017, 8:14:39 AM7/11/17
to play-fr...@googlegroups.com
Weird.  I'd probably next try putting in some println()s -- probably one in the constructor of ApplicationController to make sure the params look okay, and a couple in error() to see what things look like before it invokes languageAction, and maybe a try/catch around languageAction itself, to see if that's at all informative...

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/ba9b91ee-aa8b-475f-afc0-139a5cfc1305%40googlegroups.com.

Yang Wong

unread,
Jul 12, 2017, 9:20:15 AM7/12/17
to Play Framework
Thanks mate I'll give that a go.

Chris Nappin

unread,
Jul 13, 2017, 3:54:12 AM7/13/17
to Play Framework
Hi,

  Mockito works by having to set up front "expectations" in your unit test - such as what certain methods of your mocked object will return when invoked with certain arguments. If no expectation is set the default behaviour is to return null or 0, which is probably where your NPE is coming from.

If this is a unit test, what is the "unit" under test? If it is the controller then yes you will have to setup expectations for all mocks used by the controller. If the unit under test is your custom action then I'd recommend creating some test controllers and injecting the real action into them. To get Play to instantiate an instance of a class that uses DI, plus handle any dependencies, you can use the "app.injector.instanceOf[T]" syntax or the Injecting trait.

If this is a functional test, then you probably want to wire up a real custom action instance with a real controller, rather than using mocks.

For an example of unit testing Play 2.6 format custom action builders, please see https://github.com/chrisnappin/play-recaptcha/blob/master/test/com/nappin/play/recaptcha/NonceActionBuilderSpec.scala

For an example of a functional test with a real controller, real custom action builder, and mocked external dependency (the mocked verifier), please see https://github.com/chrisnappin/play-recaptcha-v2-example/blob/master/test/controllers/InvisibleFormSpec.scala

Hope this helps!

Cheers,

  Chris
Reply all
Reply to author
Forward
0 new messages