powered by Slim Framework
Follow @NesbittBrian rss

Play framework sample application with JWebUnit and synchronous ajax

Oct 16, 2011

I had posted awhile ago on the Play framework google group that I had successfully started using JWebUnit for testing rather than the bundled selenium suite. At the time it was partly because the IDE autocomplete support for the test helper assert functions in JWebUnit were way more convenient compared to learning the selenium commands. It also runs headless which makes CI easier, although Play 1.2 had already fixed that for selenium. That post, after sitting dormant for awhile, recently had another user trying to get an ajax test working but was running into a timing issue. Before you can assert the result of the ajax call you need to wait for the call to be completed. By default, the ajax calls are performed asynchronously, so you don't have much choice but to call Thread.sleep(X) and hope you select an appropriate value for X. This type of test is pretty fragile in my mind and in the future could possibly break and raise some false negatives about the underlying functionality.

Sure enough there was a solution already in place. Registering an instance of NicelyResynchronizingAjaxController as the testing engine ajax controller makes the ajax call synchronous and removes the dependency on the Thread.sleep(x) call. I reposted my findings and that user was then off to "write tons of code lines".

Shortly after another user requested if someone could share a sample application showing how to make it all work together as our posts had bits and pieces of code here and there. So I am about to share a sample and explain the tests a little. I won't go into much detail about the rest of the sample (its pretty simple) but you can always ask questions if needed in the comments/email/twitter/google group and I'll try to help.

For reference, here is the google group thread.

Stupid simple Events sample application

https://github.com/briannesbitt/PlaySampleWithJWebUnitWithAjax

The application is very simple and only has 1 page. It allows a user to create a new Event (id,title) and uses ajax to submit that to the server. A notification result (OK,ERROR) is displayed for the user. All existing Events are listed, and dynamically updated, at the bottom in a simple <ul> list. The application uses the bundled in-memory database H2 so there really isn't anything to setup or configure. The json responses from the server are in the following form:


{ "status": "OK", "msg": "some message" }
{ "status": "ERROR", "msg": "some message" }

Try it out!


git clone git://github.com/briannesbitt/PlaySampleWithJWebUnitWithAjax.git
cd PlaySampleWithJWebUnitWithAjax
play autotest

You can also run play test and then browse to http://127.0.0.1:9000 to try it or http://127.0.0.1:9000/@tests to run the tests manually.

Lets look at the tests

I have a BaseFunctionalTest class that extends the Play framework FunctionalTest. This creates the WebTester, configures the default browser to mimic and initializes the base url using setBaseUrl(). There is a JWebUnit quick start guide if you need to familiarize yourself first.


public abstract class BaseFunctionalTest extends FunctionalTest
{
   protected WebTester wt;
   protected BrowserVersion defaultBrowserVersion = BrowserVersion.INTERNET_EXPLORER_8;

   @Before
   public void before()
   {
      wt = new WebTester();
      wt.getTestContext().setUserAgent(defaultBrowserVersion.getUserAgent());
      if (wt.getTestingEngine() instanceof HtmlUnitTestingEngineImpl)
      {
         ((HtmlUnitTestingEngineImpl) wt.getTestingEngine()).setDefaultBrowserVersion(defaultBrowserVersion);
      }
      wt.setBaseUrl(getRouteAbsolute("Application.index"));
      wt.getTestingEngine().setIgnoreFailingStatusCodes(false);
   }
   protected String getRouteAbsolute(String action)
   {
      Router.ActionDefinition route = Router.reverse(action);
      route.absolute();
      return route.url;
   }
   protected String getRoute(String action)
   {
      return Router.reverse(action).url;
   }
}

There are 4 simple tests in the test\ApplicationTest.java file.

testIndexRendersSuccessfully()


@Test
public void testIndexRendersSuccessfully()
{
   wt.beginAt(getRoute("Application.index"));
   wt.assertElementPresent("createEvent");
   assertEquals(wt.getElementById("error").getTextContent(), "");
   assertEquals(wt.getElementById("success").getTextContent(), "");
}

This first test is a simple test to ensure the index page gets rendered properly and just checks a few html elements on the page.

testCreateEventFailsWithBlankTitle()


@Test
public void testCreateEventFailsWithBlankTitle() throws InterruptedException
{
   wt.beginAt(getRoute("Application.index"));
   wt.setTextField("title", "");
   wt.clickButton("createEvent");

   Thread.sleep(2000); // <--- Required since you have to wait for the round trip !!

   wt.assertTextInElement("error", "Required");
   assertEquals(wt.getElementById("success").getTextContent(), "");
}

The second test tries to submit the form using ajax with a blank title value. We want to check the <div id="error"> for the error message, but we have to wait for a length of time to allow the ajax call to complete. I choose 2 seconds since that seems like a reasonable amout of time.

testCreateEventSuccessAjaxAsync()


@Test
public void testCreateEventSuccessAjaxAsync() throws InterruptedException
{
   wt.beginAt(getRoute("Application.index"));
   wt.setTextField("title", "My New Event");
   wt.clickButton("createEvent");

   Thread.sleep(2000); // <--- Required since you have to wait for the round trip !!

   assertEquals(wt.getElementById("error").getTextContent(), "");
   wt.assertTextInElement("success", "Created Event with Id:");
   assertEquals(1, Event.count());
}

The third test successfully creates a new Event via ajax. Again we need to Thread.sleep(2000) to wait for the ajax call to return so we can assert the <div id="success"> gets populated with the success nofication text and that the DB has an Event.

testCreateEventSuccessAjaxSync()


@Test
public void testCreateEventSuccessAjaxSync()
{
   wt.beginAt(getRoute("Application.index"));

   // This will make the ajax call synchronous - no more Thread.sleep() !
   if (wt.getTestingEngine() instanceof HtmlUnitTestingEngineImpl)
   {
      ((HtmlUnitTestingEngineImpl) wt.getTestingEngine()).getWebClient().setAjaxController(new NicelyResynchronizingAjaxController());
   }

   wt.setTextField("title", "My New Event Title");
   wt.clickButton("createEvent"); // <--- ajax call becomes synchronous
   assertEquals(wt.getElementById("error").getTextContent(), "");
   assertEquals(1, Event.count());
   List events = Event.findAll();
   wt.assertTextInElement("success", "Created Event with Id:" + events.get(0).id);
}

The final test successfully creates a new Event via ajax, but this time we have setup an instance of NicelyResynchronizingAjaxController as the testing engine ajax controller. This makes the ajax call synchronous. This allows us to avoid the unknown length of Thread.sleep() time and we can continue our test ensuring that the <div id="success"> gets populated correctly and infact there is a new Event in the db.

I would of course push this code to the base class but have left it here in the test for the purposes of this sample.

This is not meant to be an exhaustive test suite, but simply serves to show how to setup JWebUnit to process ajax requests synchronously to make your tests more robust and increase their dependability.

%test.play.pool=2

Also just thought I would mention this since it took me a moment to realize what was happening when I first started using JWebUnit. When you run play in dev or test mode it by default only creates the server execution pool with 1 thread. When I ran my first JWebUnit functional test it worked when testing against the http://www.playframework.org homepage but failed (read hung forever) when I started using http://127.0.0.1:9000. I realized that the 1 executor thread was responding to my test run and I was causing a deadlock when making the test call back to the server again. Adding the line %test.play.pool=2 to the application.conf was the easy solution.

How much code do you read?  Home My Father Son relationship: a speech to my Dad on his 70th birthday  
blog comments powered by Disqus