Saturday, June 18, 2016

Unit testing in Play

Play testing may be done on different levels: unit tests, functional tests, server test etc. - as it is mentioned in Play testing manual. The focus in this post is on writing of unit tests.

Helpers and fakeApplication

Class Helpers is used a lot for unit tests. It imitates a Play application, fakes HTTP requests and responses, session, cookies - all whatever may be needed for tests. A controller under the test should be executed in a context of a Play application. The Helpers method fakeApplication provides an application for running tests. In order to use Helpers and fakeApplication a test class should derive from WithApplication.

The following Helpers API-s should be used:
Helpers.running(Application application, final Runnable block);

Test with Helpers looks like this:
public class TestController extends WithApplication {
     public void testSomething() {  
         Helpers.running(Helpers.fakeApplication(), () -> {  
             // put test stuff  
             // put asserts  
Adding import statements for Helpers methods makes code more compact:
import static play.test.Helpers.fakeApplication;
import static play.test.Helpers.running;
     public void testSomething() {  
          running(fakeApplication(), () -> {  
               // put test stuff  
               // put asserts  

Testing controllers

Let's call a controller method, which is bound to the particular URL in the routes as a "routed" method. An invocation of a routed method is called a controller action and has a Java type Call.
Play builds so-called reverse route to each action. Call to a reverse route creates an appropriate Call object. This reverse routing mechanism is used for testing controllers.

To invoke a controller action from test the following Helpers API should be used:
Result result = Helpers.route(Helpers.fakeRequest(Call action));

Controller tests example

1. The routes:
GET /conference/:confId    controllers.ConferenceController.getConfId(confId: String)  
POST /conference/:confId/participant controllers.ConferenceController.addParticipant(confId:String) 
2. Generated reverse routes:
3. The method getConfId is bound to GET and does not receive a body in a request. It may be invoked for test with:
Result result = Helpers.route(Helpers.fakeRequest(controllers.routes.ConferenceController.getConfId(conferenceId)));
4. The method addParticipant is bound to POST. It expects to receive a body in a request. Its invocation in test should be done like this:
ParticipantDetails inputData = DataSimulator.createParticipantDetails();
Call action = controllers.routes.ConferenceController.addParticipant(conferenceId);
Result result = route(Helpers.fakeRequest(action).bodyJson(Json.toJson(inputData));

Mocking with PowerMock

To enable mocking a test class should be annotated as following:
 @PowerMockIgnore({"*", "javax.crypto.*"})
 public class TestController extends WithApplication {

Mocking of a controller action

A controller call is mocked with RequestBuilder:
 RequestBuilder fakeRequest = Helpers.fakeRequest(action); 
For the above addParticipant an action is mocked with:
 RequestBuilder mockActionRequest = Helpers.fakeRequest(controllers.routes.ConferenceController.addParticipant(conferenceId)); 
To invoke the controller method:
 Result result = Helpers.route(mockActionRequest);
The test:
 public void testLoginOK() {
     Helpers.running(Helpers.fakeApplication(), () -> {
          ///*whatever mocking*/Mockito.when(...).thenReturn(...);
          RequestBuilder mockActionRequest = Helpers.fakeRequest(
          Result result = Helpers.route(mockActionRequest);
          assertEquals(OK, result.status());

Mocking of an action with JSON body

Let's suppose, that an input is an object of type T.
The action request mocking may be done in several ways.

Option 1:
 public static <T> RequestBuilder fakeRequestWithJson(T input, String method, String url) {  
      JsonNode jsonNode = Json.toJson(input);  
      RequestBuilder fakeRequest = Helpers.fakeRequest(method, url).bodyJson(jsonNode);  
      System.out.println("Created fakeRequest="+fakeRequest +", body="+fakeRequest.body().asJson());  
      return fakeRequest;  
Option 2:
 public static <T> RequestBuilder fakeActionRequestWithJson(Call action, T input) {  
      JsonNode jsonNode = Json.toJson(input);  
      RequestBuilder fakeRequest = Helpers.fakeRequest(action).bodyJson(jsonNode);  
      System.out.println("Created fakeRequest="+fakeRequest +", body="+fakeRequest.body().asJson());  
      return fakeRequest;  

Mocking of an action with Base authentication header

The action request mocking:
 public static final String BASIC_AUTH_VALUE = "";
 public static RequestBuilder fakeActionRequestWithBaseAuthHeader(Call action) {
      String encoded = Base64.getEncoder().encodeToString(BASIC_AUTH_VALUE.getBytes());
      RequestBuilder fakeRequest = Helpers.fakeRequest(action).header(Http.HeaderNames.AUTHORIZATION,
                                                 "Basic " + encoded);
      System.out.println("Created fakeRequest="+fakeRequest.toString() );
      return fakeRequest;

Mocking of an action with session

The action request mocking:
 public static final String FAKE_SESSION_ID = "12345";
 public static RequestBuilder fakeActionRequestWithSession(Call action) {
      RequestBuilder fakeRequest = RequestBuilder fakeRequest = Helpers.fakeRequest(action).session("sessionId", FAKE_SESSION_ID);
      System.out.println("Created fakeRequest="+fakeRequest.toString() );
      return fakeRequest;
The Play Session class is just an extension of the HashMap<String, String>. It may be mocked with simple code:
 public static Http.Session fakeSession() {  
      return new Http.Session(new HashMap<String, String>());  

Testing with Guice

See the dedicated post Using Guice for Play tests

Test configuration

Tests may run with a configuration different from an application configuration. See this post how to define a test configuration.

Unit tests execution

See the dedicated post Play unit tests execution


Henning said...

As for Fake Application replacement See
Please keep up the good work.

Ala said...

Thanks for the feedback.

sandeep saxena said...

I preview this blog. Most of the points are very interesting to read. its help me to study also. Thanks for your help.
core java training in chennai
core java training institutes in chennai
core java Training in Velachery
clinical sas training in chennai
Spring Training in Chennai
QTP Training in Chennai
Manual Testing Training in Chennai

About the author

My Photo
I trust only simple code and believe that code should be handsome. This is not a matter of technology, but professional approach, consolidated after years of software development. I enjoy to cause things working and feel very happy, when I manage to solve a problem.
Back to Top