Defining tests in Play Framework

on waitingforcode.com

Defining tests in Play Framework

Previously we discovered how to setup test environment in Play Framework. Now we can focus on more pragmatic aspect, test writing.

The article will be composed by 2 parts. Each part will cover one testing subject. The first part will cover more normal aspects of test writing in Play Framework. After that we'll discover functional tests definition with FluentLenium.

Testis in Play Framework

Basic testing tools in Play Framework are placed in play.test package. Probably one of the most often used class is Helpers. It's composed only by static methods which can be used to, for example, convert Result instance returned by one controller's method to String (contentAsString(Result result)). These methods allow us also to inspect request by getting cookies (cookie(String name, Result result)), sesssion data (session(String name, Result result)) or HTTP headers (headers(Result result)). We can also check the response status by calling status(Result result). Helpers contains also a very useful method to launch server and test the application in it. This method's name is running. Test server also can be started directly from Helpers with startServer(int port).

But running can also take different signature. And in this signature it accepts a parameter being an instance of FakeApplication class. This object replaces usual Application object, needed by Play to run the program. FakeApplication is a kind of mock adapted to test environment in Play. In play.test package exists WithApplication class. If test case extends it, it won't need to define FakeApplication manually. This object will be initialized automatically by WithApplication's provideFakeApplication() method.

Now when we know the majority of test cases in Play Framework, we can start to write sample test to check if view returned by the 1st category contains two products:

public class CategoryControllerTest {

  @Test
  public void checkProductsList() {
     /**
      * FakeApplication is a kind of mocked application context. Without it we won't 
      * able to run controllers. By running them by simple execution 
      * (Result result = CategoryController.showOne(1, 1)) you'll receive RuntimeExceptions 
      * of following
      * type:
      * <pre>
      * java.lang.RuntimeException: There is no started application
      * </pre>
      *  
      * If one class extends WithApplication, we don't need to create 
      * FakeApplication manually. This object is automatically created by 
      * provideFakeApplication() method of WithApplication class and is accessible
      * as a protected field called app. 
      *  
      * We don't do that because of connection problems to test database. If we 
      * use manually made FakeApplication, the test case is able to connect to 
      * play_store_test. But if we use app field and extends the class with 
      * WithApplication, the test can't connect to test database.
      */  
    FakeApplication fakeApp = Helpers.fakeApplication();

    final Map<String, String> threads = new HashMap<String, String>();
    threads.put("current", Thread.currentThread().getName());
    running(fakeApp, new Runnable() {
      @Override
      public void run() {
        /**
          * We need to use once again a JPA.withTransaction to avoid following RuntimeException:
          * <pre>
          * java.lang.RuntimeException: No EntityManager bound to this thread. 
          * Try wrapping  this call in JPA.withTransaction, or ensure that the 
          * HTTP context is setup on this thread.
          * </pre>
          */ 
          JPA.withTransaction(new F.Function0<Void>() {
            @Override
            public Void apply() throws Throwable {
              Result result = CategoryController.showOne(1, 1);
              String stringResult = contentAsString(result);
              // check if bread and butter are present as products
              String[] expectedProducts = {"<a href=\"/categories/1/products/bread_1\">bread", 
                              "<a href=\"/categories/1/products/butter_2\">butter"};
              for (String expectedProduct : expectedProducts) {
                assertTrue("Response should contain "+expectedProduct+" but it doesn't",
                  stringResult.contains(expectedProduct));
              }
              threads.put("runnable", Thread.currentThread().getName());
                return null;
            }
          });
    });

    /**
     * Even if the test case is written as Runnable anonymous class, it 
     * will be executed in the same thread as the main thread. So they won't 
     * be the concurrency testing issues and synchronization problems between 
     * the main test thread and other testing threads. Usually they need to be 
     * synchronized manually with CountDownLatch, CyclicBarrier or another technique. 
     * You can learn more about synchronization in multi-threading environment through 
     * this article:
     * http://www.waitingforcode.com/java/java-concurrency/tests-inmulti-threading-environment-with-junit/read
     */
    assertTrue("Test case within running() should be executed in the same thread as the main ("+threads.get("current")+") "+
                    "but it wasn't ("+threads.get("runnable")+")", threads.get("current").equals(threads.get("runnable")));
  }

}

Browser-based tests in Play Framework

Another kind of tests which we can execute in Play Framework are tests based on browser. The browser is simulated and is represented by play.test.TestBrowser object. It's a mix of Selenium's WebDriver and FluentLenium API. It inherits all methods from org.fluentlenium.core.Fluent, thanks to which it can: execute script (executeScript()), fill some form fields (fill, find elements on the page (find) or even take screenshots of simulated browser (takeScreenShot).

To simulate a browser in Play Framework tests we need to use test server instead of FakeApplication instance in running method. Test server can be simply create with Helpers testServer method. The second parameter set to running must be browser to use. Once again, Helpers comes with the help by providing some of constants with the names of supported browsers (FIREFOX, HTMLUNIT). The last parameter is an instance of Play's F.Callback object.

Let's see some of browser-based test on following example:

public class UserControllerTest  {

  private static final int SERVER_PORT = 3333;
  
   /**
    * Checks if anonymous user can't see dashboard page. If anonymous 
    * user accesses a resource behind /user path (as for example /user/dashboard), 
    * he should be redirected to connexion page (/login).
    */
  @Test
  public void dashboardAccessDenied() {
    running(testServer(SERVER_PORT), HTMLUNIT, new Callback<TestBrowser>() {
      public void invoke(TestBrowser browser) {
        // indicate to browser to access protected resource as anonymous user
        browser.goTo("http://localhost:3333/user/dashboard");

        // we check if connection form is present at the page. find method supports CSS selectors.
        assertTrue("Page should end with /login path", browser.url().endsWith("/login"));
        FluentList<FluentWebElement> loginField = browser.find("#login");
        FluentList<FluentWebElement> passwordField = browser.find("#login");
        assertTrue("Login field should be found on the page but it wasn't", 
          loginField != null && loginField.size() == 1);
        assertTrue("Password field should be found on the page but it wasn't", 
          passwordField != null && passwordField.size() == 1);
      }
    });
  }
        
   /**
    * Checks if connected user can see dashboard page.
    */
  @Test
  public void dashboardConnection() {
    running(testServer(SERVER_PORT), HTMLUNIT, new Callback<TestBrowser>() {
        public void invoke(TestBrowser browser) {
          browser.goTo("http://localhost:3333/user/dashboard");
          
          // we check if connection form is present at the page
          assertTrue("Page should end with /login path", browser.url().endsWith("/login"));
          FluentList<FluentWebElement> loginField = browser.find("#login");
          FluentList<FluentWebElement> passwordField = browser.find("#login");
          assertTrue("Login field should be found on the page but it wasn't", 
            loginField != null && loginField.size() == 1);
          assertTrue("Password field should be found on the page but it wasn't", 
            passwordField != null && passwordField.size() == 1);

          // fill connection form with user data and submit the form at the end - always using CSS selectors
          browser.fill("#login").with("bartosz2");
          browser.fill("#password").with("bartosz2");
          browser.submit("#loginForm");
          
          // check if user was correctly connected
          assertTrue("Page should end with /user/dashboard path", 
            browser.url().endsWith("/user/dashboard"));
        }
    });
  }

}

This article shows us that we don't need to limit test cases in Play Framework to simple unit tests verifying the correctness of methods execution without execution context. We can also made some integration tests by invoking directly controller's method and check returned Result object. We can also write functional tests with the help of FluentLenium to check if view part with backend one are working together correctly.

Share on: