Controller Test with FakeRequest withFormUrlEncodedBody fails

1,335 views
Skip to first unread message

Ant Kutschera

unread,
Mar 19, 2013, 5:09:09 PM3/19/13
to play-fr...@googlegroups.com

The bug was closed.  Point #2 says:

"You are invoking the action directly. The action returns an iteratee, and this iteratee must be fed the body, but you are feeding nothing into it, because you call the run method on it. This is why the body is not passed. Instead, use play.api.test.Helpers.route, this will feed the body you supplied to withFormUrlEncodedBody into the iteratee for you."

In the bug report, I clearly stated that I need to test the action directly, because I want to stub/mock dependencies which the controller has.

Could I do the stubbing/mocking AND use the Helpers.route function?  I was hoping to write a unit test, but this is much more of an integration test...

Ant Kutschera

unread,
Mar 19, 2013, 5:17:31 PM3/19/13
to play-fr...@googlegroups.com
PS The code doesn't use a dependency injection framework which I could configure to inject stubbed dependencies.


Ant Kutschera

unread,
Mar 19, 2013, 5:23:02 PM3/19/13
to play-fr...@googlegroups.com
PPS, how is specs so different to JUnit, that it requires a different setup?

Ant Kutschera

unread,
Mar 19, 2013, 6:03:12 PM3/19/13
to play-fr...@googlegroups.com
PPPS Both http://www.playframework.com/documentation/2.1.0/ScalaTest and http://www.playframework.com/documentation/2.1.0/ScalaFunctionalTest show how to test controllers directly and do not mention anything about having to use the route function.  Is this a requirement because my controller method works with Async and parallel code, or is this a bug in Play which needs fixing?  The documentation at least needs an update to clarify this.

James Roper

unread,
Mar 20, 2013, 12:39:25 AM3/20/13
to play-fr...@googlegroups.com
If you have a look at the signature of your action, it is this:

RequestHeader => Iteratee[Array[Byte], Result]

RequestHeader has no body.  So although FakeRequest, which extends RequestHeader, does have a body, your action will ignore it, since it only sees RequestHeader.  The body on FakeRequest is taken by Helpers.route, serialised into a byte array, and then fed into the returned Iteratee, which is the body parser for your action.  That's how Helpers.route passes the body to your action.  Since you're just invoking Iteratee.run, which means you are feeding nothing into the action, your action is getting no body.  So, one option would be for you to serialise the body you want and feed it into the iteratee.

Yes, you can use stubbing/mocking and use the Helpers.route function.  FakeApplication accepts a Global, you can create a mock Global that implements getControllerInstance, and inject stubs there.

Another option would be for us to provide a Helpers.call(EssentialAction, FakeRequest) method that did this.  Have to think a bit more about that.

James Roper

unread,
Mar 20, 2013, 12:40:58 AM3/20/13
to play-fr...@googlegroups.com
On Wednesday, March 20, 2013 8:23:02 AM UTC+11, Ant Kutschera wrote:
PPS, how is specs so different to JUnit, that it requires a different setup?


JUnit and Specs are completely different frameworks, with completely different ways of doing things.  The WithApplication that you were using is an Around specs, it implements something similar to the @Before and @After annotations in JUnit.  Definitely should not be used within a JUnit test. 

James Roper

unread,
Mar 20, 2013, 12:43:13 AM3/20/13
to play-fr...@googlegroups.com
On Wednesday, March 20, 2013 9:03:12 AM UTC+11, Ant Kutschera wrote:
PPPS Both http://www.playframework.com/documentation/2.1.0/ScalaTest and http://www.playframework.com/documentation/2.1.0/ScalaFunctionalTest show how to test controllers directly and do not mention anything about having to use the route function.  Is this a requirement because my controller method works with Async and parallel code, or is this a bug in Play which needs fixing?  The documentation at least needs an update to clarify this.

Yes, the documentation does need to be updated to clarify this, the information about unit testing controllers like that is just wrong. 

Ant Kutschera

unread,
Mar 20, 2013, 3:34:26 PM3/20/13
to play-fr...@googlegroups.com
Hi James, 

Thanks for the quick feedback.

It turns out that I don't need to use a global in the fake app, since I can just setup the stubs in the controller objects before calling "route".  Not sure why I didn't grasp that earlier, it's obvious really :-)

Is the recommendation then to never call a controller method directly, or only in cases where AsyncResults are returned?  If you could get the docs updated, that would be super.


Another option would be for us to provide a Helpers.call(EssentialAction, FakeRequest) method that did this.  Have to think a bit more about that.

 I guess it might be nice to be able to test the controller without the routes, e.g. I wouldn't have to set the URL in the fake request to some ugly string which causes a dependency between the routes file and my tests.  But I can live with the given solution.

Thanks,
Ant

PS The test now looks like this and runs successfully:

class ValidationControllerTest extends play.test.WithApplication {

    @Test
    def testValidate {

        import scala.collection.JavaConversions._

        val fakeApp = new play.test.FakeApplication(
                new java.io.File("."),
                classOf[FakeApplication].getClassLoader,
                new java.util.HashMap,
                List("ch.maxant.scalabook.play20.plugins.xasupport.XASupportPlugin"),
                null)

        start(fakeApp)

        //setup inputs
        val request = FakeRequest(POST, "/validate").
            withSession(ValidationController.EventUid -> "LKJSC-W1").
            withSession(Secured.Username -> "la...@hippodrome-london.com").
            withHeaders("content-type" -> "application/x-www-form-urlencoded").
            withFormUrlEncodedBody(ValidationController.BookingRef -> "bookingRefAsdf")

        //setup dependencies
        ValidationController.ticketGateway = TicketGatewayStub
        ValidationController.validationRepository = ValidationRepoStub
        ValidationController.userRepository = UserRepoStub

        //call to method under test
        val result = route(request).get
        
        //assertions
        val c = contentAsString(result) //waits for async result
        val s = status(result)
        assertEquals(OK, s)
        assertTrue(c.contains("bookingRefStillOpen"))
        assertFalse(c.contains("bookingRefAsdf"))
    }

Reply all
Reply to author
Forward
0 new messages