Objectives

Explore the Unit test features of Play by writing some tests to verify the current behavior of the Residence model class for the MyRent app. Develop a test app to consume MyRent service API, progressively implementing a range of functionality in writing to and reading from the service database.

Simple Test Harness

You should already have Eclipse + Play framework installed on your laptops. For the main part of this lab. Alternatively, you may develop on the NUCs.

You may have seen unit testing in Eclipse for simple Java applications. There is an equivalent for the Play Framework. Unit testing is particularly important if you make any changes to the model, as it allows you to verify if the model is defined correctly.

In a terminal (command line) in a suitable location run the following command and accept the default as the application name (myrent-service-test-2016):

play new myrent-service-test-2016

Change into the project folder and run these commands:

play deps
play eclipsify

Import the project into Eclipse. Explore the file structure in the navigation panel on the left. It should be similar to that shown in Figure 1.

Figure 1: Initial file structure

Download this lib.zip into a temporary folder, expand, and move to the root of myrent-service-test. Then add its contents to the build path as shown in Figure 2. It contains the various library files required, such as those for Retrofit and Gson.

Figure 2: Add contents of lib to build path

Residence model

It is necessary to override the Object equals method in Residence model. This is required to facilitate comparison of Residence instances. Here is the refactored class:


package models;

import java.util.Date;
import java.util.Random;

import com.google.common.base.Objects;

public class Residence
{

  public Long id;
  public String geolocation;
  public Long date;
  public boolean rented;
  public String tenant;
  public double zoom;
  public String photo;


  public Residence()
  {
    id = unsignedLong();
    date = new Date().getTime();
    geolocation = "";
    date = 0L;
    rented = false;
    zoom = 0;
    photo = "";
  }

  /**
   * Generates a long greater than zero
   * @return Unsigned Long value greater than zero
   */
  private Long unsignedLong() {
    long rndVal = 0;
    do {
      rndVal = new Random().nextLong();
    } while (rndVal <= 0);
    return rndVal;
  }
  @Override
  public boolean equals(final Object obj)  {
    if (obj instanceof Residence)  {
      final Residence other = (Residence) obj;
      return Objects.equal(id, other.id) 
          && Objects.equal(geolocation, other.geolocation)
          && Objects.equal(date, other.date)
          && Objects.equal(rented, other.rented)
          && Objects.equal(tenant, other.tenant)
          && Objects.equal(zoom, other.zoom)
          && Objects.equal(photo, other.photo);
    }
    return false;
  }
}

Controllers

We shall add the service api and proxy classes to the default controller package. The file structure is now as shown in Figure 1:

Figure 1: controllers package populated with api & proxy classes

package controllers;

import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import models.Residence;
import retrofit.Call;
import retrofit.GsonConverterFactory;
import retrofit.Response;
import retrofit.Retrofit;

public class ResidenceServiceAPI
{
  private String service_url = "http://localhost:9000";
  private ResidenceServiceProxy service;

  public ResidenceServiceAPI()
  {
    Gson gson = new GsonBuilder().create();

    Retrofit retrofit = new Retrofit.Builder().baseUrl(service_url)
                             .addConverterFactory(GsonConverterFactory
                             .create(gson))
                             .build();
    service = retrofit.create(ResidenceServiceProxy.class);
  }

  public List<Residence> getResidences() throws Exception
  {
    Call<List<Residence>> call = (Call<List<Residence>>) service.getResidences();
    Response<List<Residence>> residences = call.execute();
    return residences.body();
  }

  public Residence getResidence(Long id) throws Exception
  {
    Call<Residence> call = service.getResidence(id);
    Response<Residence> residence = call.execute();
    return residence.body();
  }

  public Residence createResidence(Residence newResidence) throws Exception
  {
      Call<Residence> call = (Call<Residence>) service.createResidence(newResidence);
      Response<Residence> returnedResidence = call.execute();
      return returnedResidence.body();
  }

  public int deleteResidence(Long id) throws Exception
  {
    Call<String> call = service.deleteResidence(id);
    Response<String> val = call.execute();
    return val.code();
  }

  public Residence updateResidence(Residence residence) throws Exception {
    Call<Residence> call = (Call<Residence>)service.updateResidence(residence);
    Response<Residence> returnedResidence = call.execute();
    return returnedResidence.body();
  }
}
package controllers;

import java.util.List;

import models.Residence;
import retrofit.Call;
import retrofit.http.Body;
import retrofit.http.DELETE;
import retrofit.http.GET;
import retrofit.http.POST;
import retrofit.http.Path;

public interface ResidenceServiceProxy
{


  // Residence
  @POST("/api/residence")
  Call<Residence> createResidence(@Body Residence residence);

  @DELETE("/api/residences/{id}")
  Call<String> deleteResidence(@Path("id") Long id);

  @POST("/api/residence/update")
  Call<Residence> updateResidence(@Body Residence residence);

  @GET("/api/residences")
  Call<List<Residence>> getResidences();

  @GET("/api/residences/{id}")
  Call<Residence> getResidence(@Path("id") Long id);
}

Test class

Create a class ResidenceTest in the default test package and populate with setup data as shown here. Observe that we are writing 8 residence instances to the service database.



import org.junit.Before;

import controllers.ResidenceServiceAPI;
import models.Residence;

public class ResidenceTest {
  private static ResidenceServiceAPI service = new ResidenceServiceAPI();

  private int NUMBER_residences = 8;

  static Residence residences[] = 
    { 
        new Residence(), 
        new Residence(), 
        new Residence(), 
        new Residence(),
        new Residence(), 
        new Residence(), 
        new Residence(), 
        new Residence(),
    };

}

  /**
   * Create an array of residences.
   * @throws Exception
   */
  @Before
  public void setup() throws Exception {
    service.createResidence(residences[0]);
    service.createResidence(residences[1]);
    service.createResidence(residences[2]);
    service.createResidence(residences[3]);
    service.createResidence(residences[4]);
    service.createResidence(residences[5]);
    service.createResidence(residences[6]);
    service.createResidence(residences[7]);
  }

Now add a teardown method to clean up at the end of the test run:

  /**
   * Clean up following tests.
   * @throws Exception
   */
  @After
  public void teardown() throws Exception {
    for (int i = 0; i < residences.length; i += 1) {
      service.deleteResidence(residences[i].id);
    }
  }

Let's now add a single test. This will fetch the entire list of residences across the network and check that the number corresponds to the setup number.

  /**
   * Obtain entire collection of residences
   * @throws Exception
   */
  @Test
  public void getResidences() throws Exception {
    List<Residence> list = service.getResidences();
    assertEquals(list.size(), NUMBER_RESIDENCES);
  }

These import statments are required.

import static org.junit.Assert.assertEquals;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import controllers.ResidenceServiceAPI;
import models.Residence;

Before running this test it is necessary to create a run configuration as shown here in Figure 1. But first ensure that the JUnit 4 jar is present and has been added to the build path.

Figure 1: JUnit test run configuration

Figure 2 shows how to run the test.

Figure 2: How to run unit test

A successful test should result in a window display similar to that shown in Figure 3.

Figure 3: Successful test

More tests

To conclude this short set of tests, add the following:

  • getResidence: gets a single residence.
  • deleteResidence: deletes a single residence.
  • updateResidence: updates a single residence.

Here is the code to add to ResidenceTest:

/**
 * Check the getResidence api
 * @throws Exception
 */
 @Test
 public void getResidence() throws Exception {
   Residence residence = service.getResidence(residences[0].id);
   assertEquals(residence.id, residences[0].id);
 }

 /**
  * Delete a single residence
  * @throws Exception
  */
 @Test
 public void deleteResidence() throws Exception {
   Long residenceId = residences[0].id;
   int rval = service.deleteResidence(residenceId);
   assertEquals(rval, 200);
 }

 @Test
 public void updateResidence() throws Exception {
   Residence res = residences[3];
   // Make some changes
   res.photo = "Homer's portrait";
   res.rented = true;
   res.tenant = "Homer Simpson";
   Residence returnedRes = service.updateResidence(res);
   assertEquals(returnedRes, res);
 }

Run the test suite. A successful run should result in a JUnit pane output as shown in Figure 1:

Figure 1: Success at last, TG almighty, success at last

The End (Also TG).