Hi Evan,
Let me first explain the reasons why it's hard and what we're doing to address that.
Firstly, Java actions depend on thread locals to access anything about the request. So, in order to test, you need to setup those thread locals. We're investigating a number of ways to address this, one is to make Java actions more like Scala actions where the action method doesn't directly execute the action, but rather builds and returns the action to handle the request. Another idea that literally came to me last night was to introduce a syntax in the routes file where you can declare a request parameter, eg @request, and the request will be passed in there, so you don't need to rely on thread locals.
Secondly, the Play request object depends on global state, for example, to parse the session. Without a started application, accessing certain things in the request could blow up. We're addressing this in Play 3 by making request objects dumb immutable objects, rather than live objects that process things on request. So the session will be parsed and then passed to the request, rather than the request object itself parsing it - while this means each request requires more work up front to create it, this should actually be a performance benefit because the session doesn't tend to change a lot between requests on the same connection, so that means we can cache it on the connection and only reparse when it changes.
Thirdly, and this is the biggest problem I think at the moment, and that is that the body parser, and other action composition that may be necessary to be invoked before your request is invoked, depends on an application, at a minimum to instantiate them using Guice. Addressing this is difficult, as long as we continue to use annotations to specify body parsers and action composition, there's not a lot of way around this. The builder approach to building actions mentioned before however would address this, as it means action composition and body parsers would be specified in the builder, so anything the body parsers/actions needed could be injected, and mocked if necessary in tests. The @request syntax idea may also present some options there, but that still needs to be fleshed out.
So, after all that, here's the simple answer to your question:
import play.test.Helpers;
Result result = Helpers.invokeWithContext(
Helpers.fakeRequest().bodyJson(...),
() -> controllerToTest.someAction(...);
)