How To Use CasperJS with Mocked Data to Test Your Site’s UI

by Michael Scepaniak on September 5, 2014 in software development

Tidewater Glaciers - Prince William Sound

Did you know that you can use CasperJS to script, in an automated fashion, usage of your site – including clicking links and filling and submitting forms? Did you also know that, if you’ve got a site where most/all of the data is being delivered straight to the browser via Ajax-ified payloads, you can mock all of that volatile data to produce robust tests? Do you know, given that Ajax-ified site, exactly how to do data-mocking with CasperJS? If you said “no” to any of these questions, this article is for you.

On the other hand, if you get the gist of what I’m saying, but doubt the benefits of being able to automatically test the high-level interactivity of your GUI in a data-independent fashion, I suggest reading my previous article explaining why you want to use CasperJS with mocked data to test your site. Actually, if you said “no” to the first question, you should read that article first, too.

CasperJS Tutorials

The CasperJS docs are good. Very expansive. However, the most fleshed-out, complete example they provide is the Google-scraping script in the Quickstart. That’s cool and all, but the rest of the documentation refers to and hints at all of these other, very cool features built into CasperJS. Unfortunately, these features aren’t covered in the Google-scraping example. As such, you’re left to your own devices to figure out how to move beyond the Quickstart example.

Fortunately, internet to the rescue! Gary Tryan’s CasperJS tutorial on coderwall and Kentaro Wakayama’s CasperJS tutorial (on GitHub) pick up where the official CasperJS Quickstart leaves-off. If you are unfamiliar with CasperJS, I suggest that you review both Gary and Kentaro’s write-ups. They do a good job covering the basics.

However, in order to properly demonstrate how to incorporate data-mocking into your CasperJS scripts, I’m going to move beyond these basics. Also, as CasperJS has evolved, the “recipes” for writing the scripts has evolved, as well. As such, the patterns I have adopted will be subtly different than what you see in Gary and Kentaro’s examples. So, before I get to the data-mocking, I’m going to lay a lot of groundwork, covering CasperJS techniques that go beyond the basics. Please be patient.

You may find the code that is presented in this article in a more holistic form – in the accompanying casperjs-data-mocking-tutorial GitHub repository.

Project Structure

In the codebase for the site you’re testing, create a tests-casperjs directory. Inside of this directory, create two sub-directories:

  • snapshots
  • test-cases

It’s from the top-level tests-casperjs directory that we’ll eventually run our suite of CasperJS tests – with the following command:

casperjs.bat test --includes=_pre-each-test-file.js test-cases

(Just drop the .bat if you aren’t running on Windows. For Windows users, I’ve found that CasperJS runs very nicely via Cygwin.)

With this command, by default, CasperJS is going to run every script it finds in the test-cases directory. So, our “test suite” becomes every script in our target directory. Simple, but it works.

Script Outline

Let’s look at the basic outline for one of our scripts:

casper.test.begin('Test a site scenario.' /*, planned nbr of tests */, {
     setUp: function(test) {
          mikeTestCtx.testCase = 
               mikeTestUtils.calcTestCaseName(test.currentTestFile);

          mikeTestCtx.testCaseSpecificSinonMockResponses = [
          
          ];

          mikeTestCtx.testCaseSpecificMockjaxResponses = [

          ];
     }

     , tearDown: function(test) {
          mikeTestCtx.testCaseSpecificSinonMockResponses = [];
          mikeTestCtx.testCaseSpecificMockjaxResponses = [];
     }

     , test: function(test) {
          casper.start();

          /*
           * Don't run any of the tests in this script if the script has 
           * been included in the ignore list.
           */
          casper.thenBypassIf(function() {
               var testsToSkip = casper.options.testsToSkip;
               return testsToSkip.indexOf(mikeTestCtx.testCase) > -1;
          }, 999);

          casper.thenOpen(casper.options.startingUrl
          , function openStartingUrl(){
               mikeTestUtils.screenCapture(testCtx.testCase, 'start.png');
          });

          casper.then(function testThis(){
               
          });

          casper.then(function testThat(){
               
          });

          casper.run(function runTest() {
               this.log('Test finishing', 'info');

               this.test.done();
          });
     }
});

This is the entire (stripped-down) content for a test script, which should be saved in the test-cases directory (e.g., as scenario-1-test.js). Allow me to step through some notable elements of the script.

casper.test.begin('Test a site scenario.' /*, planned nbr of tests */, {

In the wrapping call to casper.test.begin, I’ve decided to punt on setting the planned argument because I’ve found that keeping this number accurate as I add tests to- and maintain the script is unrealistic. So, I just do without it. (But, I leave in the reference so I don’t forget that I can specify it if I want to.)

setUp: function(test) {
     mikeTestCtx.testCase = 
          mikeTestUtils.calcTestCaseName(test.currentTestFile);

     mikeTestCtx.testCaseSpecificSinonMockResponses = [
          
     ];

     mikeTestCtx.testCaseSpecificMockjaxResponses = [

     ];
}

, tearDown: function(test) {
     mikeTestCtx.testCaseSpecificSinonMockResponses = [];
     mikeTestCtx.testCaseSpecificMockjaxResponses = [];
}

The setUp and tearDown functions should be familiar to anyone that has prior experience writing unit tests. In the case of CasperJS, setUp is executed before every test (the casper.test.begin call) and tearDown is executed after every test (the same casper.test.begin call).

A standard step I always take at the beginning of every test case is to save a brief identifier for the test case. I’ll go into the implementation of calcTestCaseName later. As for the mikeTestCtx and mikeTestUtils objects, they are simply JavaScript objects, which I use as organization constructs. I’ll explain them in further detail later.

The main thing I use the setUp and tearDown functions for is to manage arrays of mocked response objects. Lots more detail on these is coming.

, test: function(test) {

     casper.start();

}

The test function contains the actually logic of the test case. If you’ve read Gary’s write-up, the casper.start call should not be new to you.

casper.thenBypassIf(function() {
     return casper.options.testsToSkip.indexOf(mikeTestCtx.testCase) > -1;
}, 999);

It’s standard practice for me to include a call to casper.thenBypassIf at the start of each test case. This allows me to disable a test in the suite, as needed. More details later.

casper.thenOpen(casper.options.startingUrl, function openStartingUrl() {
     mikeTestUtils.screenCapture(testCtx.testCase, 'start.png');
});

I prefer accessing my initial starting URL using the casper.thenOpen function instead of casper.start because casper.thenOpen is more flexible. The casper.thenOpen function can be used multiple times within a test case, whereas casper.start can only be used once. In practice, I don’t think I’ve ever used casper.thenOpen more than once in a single test case, but using it like so helps me to remember that it exists.

The mikeTestUtils.screenCapture function is a helper function I wrote that wraps the standard casper.capture function. Details later.

casper.then(function testThis(){

});

casper.then(function testThat(){
     
});

The majority of the test case’s logic is contained in casper.then functions. This is standard CasperJS.

casper.run(function runTest() {
     this.log('Test finishing', 'info');

     this.test.done();
});

The casper.run function and contained test.done function call are standard CasperJS.

Testing Logic

Let’s talk about those casper.then functions, because this is where the meat of our testing logic will lie. This is an example of a typical call to casper.then for me:

/*
 * Test something.
 */
casper.then(function testSomething(){
     this.click('#some-link');

     this.log('Clicked some link. New URL is ' 
          + this.getCurrentUrl(), 'info');

     /*
      * Wait for something to appear or change on-screen before 
      * proceeding.
      */
     casper.waitFor(function waitForSomething() {
          return this.evaluate(function evaluateSomething() {
                return document.querySelector('#something') !== null;
          });
     }
     , function then() {
          mikeTestUtils.screenCapture(mikeTestCtx.testCase, 'cap.png');

          this.test.assertSelectorHasText('#a-link', 'Some Text);
     });
});

This shows my typical flow:

  1. Take some action in the UI of the site being tested. This could be clicking a screen element, filling in an input field, etc.
  2. Log the activity to aid in debugging the test later.
  3. Wait for the UI to respond to the action.
  4. Once the UI has responded as expected, take a screen-shot and test the state of the UI.

You can get as convoluted as you want inside of the casper.waitFor function argument. In the example I’ve shown, I’m using a single evaluate. However, there’s nothing preventing you from checking the results of calls to multiple individual evaluate functions. Same with that evaluate function argument. You can factor the result of multiple querySelector invocations into the return value. For example:

return this.evaluate(function evaluateTwoThings() {
     var test1 = document.querySelector('#something') !== null;
     var test2 = document.querySelector('#something-else') !== null;
     return (test1 && test2);
});

Includes

When I stepped through the basic outline for a script, I mentioned repeatedly that more details were coming. Well, here they come. Take a look again at our CasperJS command:

casperjs.bat test --includes=_pre-each-test-file.js test-cases

Notice how we’re using the --includes command line option to include _pre-each-test-file.js before each test file execution. As you might expect, we want to place _pre-each-test-file.js in the top-level tests-casperjs directory. Let’s look at some of the contents of that file.

Keep in mind that the contents of our _pre-each-test-file.js file are completely unstructured. The contents are simply a bunch of JavaScript statements. For a better picture of the file as a whole, have a look at the source for _pre-each-test-file.js in the accompanying casperjs-data-mocking-tutorial GitHub repository.

It’s in the _pre-each-test-file.js file that I set various and standard CasperJS options:

  • casper.options.verbose
  • casper.options.logLevel
  • casper.options.waitTimeout
  • casper.options.clientScripts
  • casper.options.viewportSize

In addition, I also set my own custom options:

  • casper.options.testsToSkip
  • casper.options.startingUrl
  • casper.options.screenCapturePositionAndDimensions

I don’t have to set these properties on casper.options, but I feel that doing so builds on a nice pattern already established by CasperJS.

I declare casper.options.testsToSkip as an array, populated with the identifiers of any test cases I don’t want to be executed.

casper.options.testsToSkip = [
     'broken-test'
     , 'old-test'
];

Remember, casper.options.testsToSkip is referenced in the function argument passed to casper.thenBypassIf, which I typically call early on in each test case. The identifiers included in the array are built by mikeTestUtils.calcTestCaseName (details coming up in a second).

Next, I declare and “initialize” my mikeTestCtx object:

this.mikeTestCtx = {};

/*
 * Mock server responses that are specific to the context test case - 
 * using Sinon.
 */
mikeTestCtx.testCaseSpecificSinonMockResponses = [];

/*
 * Mock server responses that are specific to the context test case - 
 * using Mockjax.
 */
mikeTestCtx.testCaseSpecificMockjaxResponses = []; 

I could have also set these on casper.options. I chose to place them someplace else, though. Just a personal preference.

Next, I declare and populate my mikeTestUtils object:

this.mikeTestUtils = {};

Like mikeTestCtx, mikeTestUtils is simply a JavaScript object. I could have condensed them into one object, but I chose to split them out. Again, just a personal preference. Regardless, it is here that I define those helper methods I touched on earlier when stepping through the basic outline for a script.

/*
 * Using the output of CasperJS' test.currentTestFile, "construct" a 
 * short identifier for the given test script.
 */
mikeTestUtils.calcTestCaseName = function(testFilePath) {
     // test-cases/a-test.js
     var result = '';
     var idx1 = testFilePath.lastIndexOf('/');
     var idx2 = testFilePath.lastIndexOf('.');
     result = testFilePath.substring(idx1 + 1, idx2);
     // a-test
     return result;
};

/*
 * Wrap the standard casper.capture function, placing all snapshots in 
 * a standard directory, organized by test case, using a globally-defined 
 * capture position and dimensions.
 */
mikeTestUtils.screenCapture = function(testCase, targetFileName) {
     casper.capture(
          'snapshots/' + testCase + '/' + targetFileName
          , casper.options.screenCapturePositionAndDimensions
     );
};

It’s also in the _pre-each-test-file.js that I define the event listeners that CasperJS allows for:

casper.on('page.error', function(msg, trace) {
     this.log('Error: ' + msg, 'ERROR', 'error');
     this.log('Trace: ' + JSON.stringify(trace), 'error');
});

casper.on('error', function(msg, trace) {
     this.log('Error: ' + msg, 'ERROR', 'error');
     this.log('Trace: ' + JSON.stringify(trace), 'error');
});

casper.on('step.error', function(err) {
     this.log('Step has failed: ' + err, 'error');
});

casper.on('step.timeout', function() {
     this.log('Step has timed-out.', 'error');
});

casper.on('timeout', function() {
     this.log('timeout', 'error');
});

/*
 * When a waitFor function times-out, log the error and save a screenshot 
 * of the UI in its current state.
 */
casper.on('waitFor.timeout', function() {
     this.log('waitFor.timeout', 'error');
     mikeTestUtils.screenCapture('', 'waitFor-timeout.png');
});

/*
 * Capture the output of console.log() statements included in the 
 * JavaScript on the site that is being tested.
 */
casper.on('remote.message', function(msg) {
     this.log('    Logged in browser -> ' + msg, 'debug');
});
    
/*
 * Capture alert() statements included in the JavaScript on the site 
 * that is being tested. 
 */
casper.on('remote.alert', function(msg) {
     this.log('    Alert shown in browser -> ' + msg, 'info');
}); 

Data-Mocking

OK, enough groundwork. it’s time to get to the end purpose of this tutorial – the data-mocking. I’m using two libraries to mock data – Sinon.JS and Mockjax. For both Sinon.JS and Mockjax, data mocks need to be configured in the standard CasperJS load.finished event listener (which should be defined in _pre-each-test-file.js).

casper.on('load.finished', function(resource) {

     this.evaluate(function (testCaseSpecificSinonMockResponses
          , testCaseSpecificMockjaxResponses) 
     {
         
          /*
           * Prepare the Sinon.JS fake server for use to mock Ajax 
           * responses. Configure the server to auto-respond to each 
           * request and filter out those Ajax requests that we don't 
           * want to mock.
           */
          var server = sinon.fakeServer.create();
          server.autoRespond = true;
          sinon.FakeXMLHttpRequest.useFilters = true;
          sinon.FakeXMLHttpRequest.addFilter(
          function(method, url, async, username, password) {
               /*
                * Requests to the server for static templates should be 
                * allowed through (un-mocked).
                */
               if (url.indexOf('\/templates') === 0) {
                    // console.log('Request being let through - url = ' 
                    //      + url + ', method = ' + method);
                    return true;
               }
               else {
                    console.log('Request being mocked - url = ' 
                         + url + ', method = ' + method);
               }
          });

          testCaseSpecificSinonMockResponses.forEach(function (ele) {
               if (ele.url) {
                    server.respondWith(ele.method, ele.url, ele.response);
               }
               else {
                    server.respondWith(ele.method
                         , new RegExp(ele.regexp)
                         , ele.response);
               }
          });

          $.mockjaxClear();

          testCaseSpecificMockjaxResponses.forEach(function (element) {
               if (element.url) {
                    var idx = $.mockjax(element);
               }
               else {
                    element.url = new RegExp(element.regexp);
                    var idx = $.mockjax(element);
               }
          });

          /*
           * Prepare mock server responses that are applicable to 
           * multiple test cases.
           */
          // Add as needed.

     }, mikeTestCtx.testCaseSpecificSinonMockResponses
          , mikeTestCtx.testCaseSpecificMockjaxResponses);

});

The listener function argument is comprised mostly/totally of a call to the CasperJS evaluate function. This, presumably, is evaluated right as/after each page is loaded by CasperJS. To the evaluate function argument, we pass the Sinon.JS-specific and Mockjax-specific mock responses that we intend to define in each individual test case. Inside of the evaluate function argument, we need to configure both Sinon.JS and Mockjax to mock our Ajax requests.

Intuitively, I’d prefer to define some/all of the mock responses as-needed in each discrete casper.then step of each individual test case. However, trying to configure the mocking this “late” has never worked for me, for both Sinon.JS and Mockjax.

Sinon.JS Configuration And Use

Our use of Sinon.JS is based on its fakeServer component, which we configure to auto-respond to each request and filter out those Ajax requests that we don’t want to mock.

var server = sinon.fakeServer.create();
server.autoRespond = true;
sinon.FakeXMLHttpRequest.useFilters = true;

Once we set up the fakeServer, we then need to map each request to a response. We do this by looping through the collection of Sinon.JS mock responses passed into the evaluate function argument, handing each collection element to the fakeServer‘s standard respondWith function.

testCaseSpecificSinonMockResponses.forEach(function (ele) {
     if (ele.url) {
          server.respondWith(ele.method, ele.url, ele.response);
     }
     else {
          server.respondWith(ele.method
               , new RegExp(ele.regexp)
               , ele.response);
     }
});

Sinon.JS is now ready to match each Ajax request by method (i.e., GET, POST, etc.) and URL to the respective mock response. If every Ajax request is accounted for properly, the only requests that will make it through to the (real) server are requests for static resources (as configured in the fakeServer FakeXMLHttpRequest.addFilter function argument).

Take another look at the loop that iterates over the collection of Sinon.JS mock responses. Notice that it has some conditional logic based on the collection element currently in context. This is to allow for both simple URL matching and URL matching based on regular expressions. Let’s take a look at a few Ajax responses we would want to mock-up for a hypothetical online book store:

mikeTestCtx.testCaseSpecificSinonMockResponses = [
     {method: 'GET', regexp: 'rest-json\/user-session-data\\?_=.*',
          response: [200, { 'Content-Type': 'application/json' },
               '{"login":"test-user","accountStatus":"EXPIRED"}']}

     , {method: 'GET', url: 'rest-json/book-categories',
          response: [200, { 'Content-Type': 'application/json' },
               '[{"id":340734,"description":"Biographies"}' +
                    ',{"id":482913,"description":"Children\'s Books"}' +
                    ',{"id":404267,"description":"Graphic Novels"}' +
                    ',{"id":360628,"description":"History"}' +
                    ',{"id":434621,"description":"Home & Garden"}' +
                    ',{"id":337310,"description":"Humor"}' +
                    ',{"id":367035,"description":"Medicine"}' +
                    ',{"id":368479,"description":"Nonfiction"}' +
                    ',{"id":364578,"description":"Politics"}' +
                    ',{"id":340278,"description":"Reference"}' +
                    ',{"id":422684,"description":"Self-Improvement"}]']}

     , {method: 'POST', url: 'rest-json/purchase',
          response: [200, { 'Content-Type': 'application/json' },
               '{"transResponse":"APPROVED", "transId":"10289978"}']}
];

Recall that it’s in the setUp function for each individual test case that we define one of these mikeTestCtx.testCaseSpecificSinonMockResponses collections. In this example, the collection contains three mocked responses – two GETs and one POST. The first GET mocks a request to retrieve the user’s session info, the second GET mocks a request to retrieve the list of book categories offered by the book store, and the remaining POST mocks a request to make a purchase.

Notice that the second and third mock responses use simple url properties, whereas the first mock response uses a more powerful regexp property. This makes sense, as the front-end logic for the site we’re testing includes a cache-busting querystring in the user session request. Because this cache-buster is being randomly generated in some fashion, we have to use a regular expression to match on those applicable URL’s.

For the other GET request, caching of the request by the browser is fine, as the list of book categories is not subject to much change. As such, we can use a simple url property to match against. As for the remaining POST request, cache-busting is not a concern, as POST requests are not typically cached. Therefore, the simple url property works well here, too.

If you are wondering why I just don’t directly set the value of the url property to be a regular expression, as needed, when defining mikeTestCtx.testCaseSpecificSinonMockResponses, it’s because that won’t work for me. Neither a regular expression literal or constructed RegExp object make it through successfully. I’m not sure why. So, I do it lazily with a bit of conditional logic right at the point where I call server.respondWith.

The bottom line to remember here is that, when data-mocking Ajax requests using Sinon.JS, you’re going to need to be prepared to match on both static and dynamic URL’s. The technique I’ve demonstrated here accomplishes that.

Mockjax Configuration And Use

The configuration required by Mockjax is quite simple:

$.mockjaxClear();

testCaseSpecificMockjaxResponses.forEach(function (element) {
     if (element.url) {
          var idx = $.mockjax(element);
     }
     else {
          element.url = new RegExp(element.regexp);
          var idx = $.mockjax(element);
     }
});

On each page load, we clear out any existing Mockjax handlers. We then loop through the collection of Mockjax mock responses passed into the evaluate function argument, letting Mockjax do its thing with each. And that’s it.

As far as defining the collection of Mockjax mock responses, that happens in the setUp function for each individual test case. The Mockjax equivalent of the Sinon.JS mock responses we discussed earlier would be as follows:

mikeTestCtx.testCaseSpecificMockjaxResponses = [
     {
          regexp: 'rest-json\/user-session-data\\?_=.*'
          , type: 'GET'
          , status: 200
          , responseTime: 1
          , contentType: 'application/json'
          , responseText:
                '{"login":"test-user","accountStatus":"EXPIRED"}'
     }

     , {
          url: 'rest-json/book-categories'
          , type: 'GET'
          , status: 200
          , responseTime: 1
          , contentType: 'application/json'
          , responseText:
                '{"id":340734,"description":"Biographies"}' +
                    ',{"id":482913,"description":"Children\'s Books"}' +
                    ',{"id":404267,"description":"Graphic Novels"}' +
                    ',{"id":360628,"description":"History"}' +
                    ',{"id":434621,"description":"Home & Garden"}' +
                    ',{"id":337310,"description":"Humor"}' +
                    ',{"id":367035,"description":"Medicine"}' +
                    ',{"id":368479,"description":"Nonfiction"}' +
                    ',{"id":364578,"description":"Politics"}' +
                    ',{"id":340278,"description":"Reference"}' +
                    ',{"id":422684,"description":"Self-Improvement"}'
     }

     , {
          url: 'rest-json/purchase'
          , type: 'POST'
          , status: 200
          , responseTime: 1
          , contentType: 'application/json'
          , responseText:
                '{"transResponse":"APPROVED", "transId":"10289978"}'
     }

];

Sinon.JS And Mockjax

The differences between the two formats expected by Sinon.JS and Mockjax are mostly superficial. Both Mockjax and Sinon.JS provide the ability to modify and specify all of the same request and response attributes. However, whereas Sinon.JS likes to address the mocking holistically, Mockjax works more surgically, on a request-by-request basis.

Somewhat surprisingly, Sinon.JS and Mockjax work well together – and in the manner that you’d hope. I’ve successfully specified both Sinon.JS mock responses and Mockjax mock responses in the same test case. In practice, I’ve found that Mockjax overrides any mocking set-up via Sinon.JS. Pretty sweet.

Of course, you don’t have to use both libraries. You can choose to go all Sinon.JS or all Mockjax, thereby simplifying your mocking. I ended up using both because I started out using only Sinon.JS. I only introduced Mockjax into the mix because it offers something that Sinon.JS doesn’t – the ability to test POST bodies!

If I had to do it all over again from the beginning, it’s very possible that I’d simply go with Mockjax. But, my use of Mockjax has been limited, so I can’t say for certain.

Testing POST Bodies

By using mock responses to satisfy Ajax requests issued from the browser, you are making implicit assertions about how you expect the front-end logic (of the site you are testing) to behave. Your casper.waitFor calls will never be satisfied and move forward to call the corresponding then function arguments if the front-end logic issues unexpected Ajax requests (HTTP method + URL + querystring) or tries to process the (mocked) responses incorrectly. Instead, the CasperJS test will simply time-out.

However, even if the front-end logic correctly issues the request and correctly processes the response, when the request in question is a POST request, a lot can still go wrong. Namely, the POST body could be incorrect in some way. Sinon.JS doesn’t offer any way to get at POST bodies – but Mockjax does.

Until you call $.mockjaxClear, Mockjax keeps all of the Ajax calls it has mocked available for recall by executing $.mockjax.mockedAjaxCalls. Cool! However, I’ve found that getting this call to return anything in CasperJS can be difficult (maybe impossible).

I stated earlier that defining some/all of the mock responses as-needed in each discrete casper.then step of each individual test case never worked for me. Similarly, trying to call $.mockjax.mockedAjaxCalls from an evaluate function argument in a CasperJS test case always returns an empty array for me. As such, I’ve had to resort to some hacking.

In order to gain access to the POST bodies of Mockjax-mocked requests, I’ve had to modify the front-end logic of the site I’m testing to make the return value of the $.mockjax.mockedAjaxCalls function available to my CasperJS test cases. I know, modifying the test subject itself is a bit distasteful, but I find the functionality gained worth it.

In my case, the front-end of the site I’m testing is built using Backbone. Over the course of development, we’ve come to override/wrap the Backbone.sync function to do many things. It’s at this point in the front-end logic that I’ve placed code to save-off the mocked Ajax calls.

if ($.mockjax && $.mockjax.mockedAjaxCalls) {
    globalMockedAjaxCalls = $.mockjax.mockedAjaxCalls();
}

The globalMockedAjaxCalls variable here is intentionally global. While, as I said, calling $.mockjax.mockedAjaxCalls from an evaluate function argument in a CasperJS test case always returns an empty array, accessing the now-populated globalMockedAjaxCalls variable from the same evaluate function argument, happily, does not. Having access to the mocked Ajax calls (via globalMockedAjaxCalls) then allows me to assert the content of POST bodies, like so:

/*
 * Test POST body.
 */
casper.then(function testPosyBody(){
     this.click('#some-button');

     this.log('Clicked some button. ' 
          + 'New URL is ' + this.getCurrentUrl(), 'info');

     /*
      * Wait for something to appear or change on-screen before 
      * proceeding.
      */
     casper.waitFor(function waitForPostBody() {
          return this.evaluate(function evaluatePostBody() {
                return document.querySelector('#something') !== null;
          });
     }
     , function then() {
          mikeTestUtils.screenCapture(mikeTestCtx.testCase
               , 'post-body.png');

          /*
           * Test the POST body sent to the server when the button was 
           * clicked.
           */
          this.test.assert(this.evaluate(function evaluatePostBody() {
               var result = false;
               _.each(globalMockedAjaxCalls, function(mockedCall) {
                    console.log('mockedCall = ' 
                         + JSON.stringify(mockedCall));
                    if (mockedCall.url === 'rest-json/purchase' 
                              && mockedCall.type === 'POST') 
                    {
                         var requestData = JSON.parse(mockedCall.data);
                         // Inspect and evaluate the requestData object.
                         // If all is as expected, set result to true.
                    }
               });

               return result;
          }));
     });
});

Notice how, in the this.test.assert call, its enclosed evaluate function argument has access to the globalMockedAjaxCalls object (which is being populated by the front-end logic of the site I’m testing). I then loop through the mocked Ajax calls to find the one I want (matched by URL and HTTP method), parse its data property (the POST body), and inspect it as needed. Cool, eh?

I’m not going to go into further detail about how you should go about populating globalMockedAjaxCalls. Regardless of which front-end JavaScript library or framework you use, whether it’s open-source, closed-source, or home-grown, you should (hopefully) have a similar ability to tap into the point where it is making Ajax requests and receiving responses. You’ll have to figure out your own implementation given your own codebase and library choice.

Client Scripts

In the code I just presented demonstrating how you could set globalMockedAjaxCalls, you’ll notice that I first check for the existence of $.mockjax. This is because, when not being tested using CasperJS, Mockjax will not be available in the front-end logic, the JavaScript runtime environment, or the browser. Mockjax is simply being used for testing. How, then, does it get into the mix? If you know much about CasperJS, then you’d probably guess that I’m doing this using the CasperJS clientScripts option. If so, you’re right.

casper.options.clientScripts = [
    /*
     * Make jQuery and Underscore available for use in evaluate() blocks.
     */
    '../lib/jquery/jquery.js'
    , '../lib/underscore.js'

    /*
     * Make two different libraries available for mocking Ajax requests 
     * and responses.
     */
    , '../lib/sinon-server.js'
    , '../lib/jquery/jquery.mockjax.js'
];

I set casper.options.clientScripts in _pre-each-test-file.js. As you should fully expect, I inject both Sinon.JS and Mockjax for the purpose of data-mocking. In addition, I also inject jQuery and Underscore to make life writing tests more pleasant. Without jQuery and/or Underscore, I’d be left with only raw JavaScript and the DOM API to code my assertions. That’s pretty limiting. Once you inject jQuery and/or Underscore, you have free access to them from inside your CasperJS evaluate function arguments:

this.test.assert(this.evaluate(function evaluateSomething() {
     return $('#some-input').val() == "some text";
}));

Test Mode

The hack with which I go about setting globalMockedAjaxCalls speaks to the need of another feature on which I’ve come to rely when testing – testMode. Before running my CasperJS test suite, I put the front-end logic of the site I’m testing into “test mode”. This amounts to little more than a global switch – true or false. I’ve found this switch to be a very helpful work-around when you find that certain bits of your front-end JavaScript just doesn’t work as expected when run via CasperJS.

For example, I’ve had difficulty running tests that show a Bootstrap modal backdrop and, separately, invoke a Tipped tooltip on-hover. In both cases, the only work-around I could devise was to conditionally modify the front-end logic of the site I’m testing to, for example, disable the modal backdrop or invoke the tooltip on-click (instead of on-hover). In these cases, the conditional in question is keyed on whether testMode is set to true or false. I use the same testMode flag to determine whether or not globalMockedAjaxCalls should be set.

If you can do without instituting some such testMode, more power to you. I’d definitely rather not allow testing-specific constructs to make it into my production code, but I’ve found the risks to be low enough that the benefits outweigh the drawbacks. I’ve always found automated testing endeavors to be fraught with such judgement calls. The practice of functional testing with CasperJS is, unfortunately, not above such compromises.

Identical Requests

One final undiscussed gotcha’ that I’ll mention… If a single test case makes two of the same requests (i.e., same URL, querystring, and HTTP method), I currently have no clean way to distinguish between or sequence the requests. Therefore, I have no way to return different mock responses to those identical requests.

Over the course of my past year or so of writing functional tests using CasperJS, this has never proven to be a show-stopper. However, I’m aware that the limitation exists and I’m hopeful that an elegant solution exists, as well. You’ve been warned.

In Conclusion

If you are disappointed by some of the hacks and gotcha’s I’ve presented here, what can I say? When has data-mocking ever been perfect? We do the best we can. Fortunately, the combination of CaperJS, Sinon.JS, and Mockjax gives us a ton of power and functionality with which to work.

I very much hope that both the data-mocking and advanced CasperJS testing techniques I’ve covered here find their way into your development process and codebase. If they do, please leave a comment to let me know! They deserve more widespread usage. And if you figure out any more-refined solutions to some of the hacks I’ve covered here, please let me know about them, as well.

Thank you – and good luck!

Mike
Want to be notified when new articles are posted?
Enter your email address:

{ 6 comments… read them below or add one }

Tariq Hamid November 12, 2014 at 7:06 pm

Fantastic article!

Many thanks

Reply

Michael Scepaniak January 8, 2015 at 6:17 am

I appreciate that, Tariq.

Reply

Jake Brawley June 29, 2015 at 9:29 pm

Looking forward to experimenting the approaches you have put here on a few different e-commerce platforms(Magento and Demandware. Thanks for your contributions. Looking forward to more of your articles.

Reply

Michael Scepaniak July 3, 2015 at 12:35 pm

You’re welcome, Jake. Thanks!

Reply

Richard Grov September 2, 2015 at 7:54 pm

Hey, thanks for a wonderful article. So much stuff I’ve been trying to find. I have a question though: do I have to fake data to use AJAX? I have a site that relies on calls to an API and updates the page. I don’t need to fake this data, in fact I’d rather not. But those calls/returns do not update the page. In spite of using loooong wait statements, waitForSelector, anything I can think of, the page does not respond, no new data shows. Using Casper and Phantom. Thanks!

Reply

Michael Scepaniak September 29, 2015 at 7:43 pm

Richard,

I’ve never had a problem with this. For me, trying to catch all of the Ajax calls made from a page and mocking them with static data tends to be an incremental exercise. One-by-one, I mock each call. During this process, the unmocked calls keep working just as expected. It’s hard for me to guess at what could be causing your issue. I’m sorry.

Mike….

Reply

Leave a Comment

{ 1 trackback }

Previous post:

Next post:

Member of The Internet Defense League