How to test jquery and ajax calls using JsTestDriver?
Asked Answered
P

4

6

I have a dynamic page built with jQuery. Html pieces are loaded from mustache templates. These templates are downloaded from a url, and I would like to unit test the overall html construction :

The JsTestDriver test is :

AppTest = TestCase("AppTest")

AppTest.prototype.test = function() {
    var actualHtml = "";

    getHtml({ "title": "title", "header": "header", "text": "text", "authors": [ {"firstname": "firstname", "lastname": "lastname"} ] }, function(html) {
        actualHtml = html;
    });

    assertEquals("expected html", actualHtml);
};

And the code :

function getHtml(json, resultFunc) {
   jQuery.ajax({
            url: "url/to/mustache/template",
            success: function(view) {
                    resultFunc(mergeArticleModelAndView(json, view));
            },
            error: function(jqXHR, textStatus, errorThrown) {
                    resultFunc(textStatus + " errorThrown: " + errorThrown);
            },
            dataType: 'text',
            async: false 
    });
}

Then I launch the tests and the result is :

$ java -jar JsTestDriver-1.3.2.jar --port 9876 --browser /usr/bin/firefox --tests all
F
Total 1 tests (Passed: 0; Fails: 1; Errors: 0) (8,00 ms)
  Firefox 5.0 Linux: Run 1 tests (Passed: 0; Fails: 1; Errors 0) (8,00 ms)
    AppTest.test failed (8,00 ms): AssertError: expected "expected html" but was "error errorThrown: [Exception... \"Component returned failure code: 0x80004005 (NS_ERROR_FAILURE)\"  nsresult: \"0x80004005 (NS_ERROR_FAILURE)\"  location: \"JS frame :: http://localhost:9876/test/main/js/jquery.min.js :: <TOP_LEVEL> :: line 16\"  data: no]"
  ()@http://localhost:9876/test/test/js/app_test.js:25

So the error callback has been called, and I don't understand why it breaks with JsTestDriver, and the code works when calling the application manually with a browser

Last thing, the jsTestDriver.conf :

server: http://localhost:9876

load:
  - test/js/app_test.js
  - main/js/jquery.min.js
  - main/js/jquery.mustache.js
  - main/js/app.js

Thank you for your advices. More generally, what unit test frameworks do you use for javascript and command line testing with DOM and jQuery ?

Pleurisy answered 16/7, 2011 at 13:44 Comment(1)
I use Jasmine. I don't execute them via command-line, but there are some plugins to do that. What I like about Jasmine, is it generates prose-like test code. And you can nest your tests. There is a plugin for jQuery too, but I'm fine without it.Nozicka
P
3

Yes it is possible to do quite the same with jquery :

Test code (just the setup the test is the same) :

TestCase("AppTest", {
    setUp: function() {
        this.xhr = Object.create(fakeXMLHttpRequest);
        this.xhr.send=function () {
            this.readyState = 4;
        };
        this.xhr.getAllResponseHeaders=stubFn({});
        this.xhr.responseText="<expected data>";
        jQuery.ajaxSettings.isLocal=stubFn(true);
        jQuery.ajaxSettings.xhr=stubFn(this.xhr);
    },
// same test method

And the production code :

function getHtml(model, resultFunc) {
        $.get("/url/to/template", function(view) {
                resultFunc(mergeArticleModelAndView(model, view));
        });
}

That's the solution that we have chosen.

Pleurisy answered 19/7, 2011 at 20:34 Comment(0)
T
5

An alternative would be to get JsTestDriver to serve your template and some test JSON data. The configuration below allows you to put static JSON test data (e.g. my_data.json) in src/test/webapp/json and Mustache templates in src/main/webapp/templates. You can change this around to match your source layout if this is too Maven-y for your tastes.

Note that JsTestDriver serves the assets with /test/ pre-pended to the URI specified in the serve attribute, so src/test/webapp/json/my_data.json is actually served from http:/ /localhost:9876/test/src/test/webapp/json/my_data.json.

server: http://localhost:9876

serve:
- src/main/webapp/templates/*.html
- src/test/webapp/json/*.json

load:
- src/main/webapp/js/*.js
- src/main/webapp/js/libs/*.js

test:
- src/test/webapp/js/*.js

Then, in a test case you can easily load a template and JSON data.

    testLoadTemplateAndData : function () {

        // Variables
        var onComplete, template, data, expected, actual, templateLoaded, dataLoaded;
        dataLoaded = false;
        templateLoaded = false;
        expected = "<h2>Your expected markup</h2>";

        // Completion action
        onComplete = function () {
            if (dataLoaded && templateLoaded) {

                // Render the template and data
                actual = Mustache.to_html(template, data);

                // Compare with expected
                assertEquals('Markup should match', expected, actual);

            }
        };

        // Load data with jQuery
        $.ajax({
            url : '/test/src/test/webapp/json/demo.json', 
            success : 
                function (result) {
                    data = result;
                    dataLoaded = true;
            },
            error : 
                function () {
                    fail("Data did not load.");
            },
            complete :
                function () {
                    onComplete();
                }
            }
        );

        // Load the template with jQuery
        $.get('/test/src/main/webapp/templates/demo.html',
            function(result) {
                template = result;
                templateLoaded = true;
            }
        )
        .error(function() { fail("Template did not load."); })
        .complete(function() {
            onComplete();
        });

    }

When both jQuery callbacks complete, Mustache should parse the template with the JSON data and render the expected output.

Talc answered 31/1, 2012 at 13:20 Comment(0)
P
4

I found a way to do it : is to mock ajax calls. On http://tddjs.com/ there is an example to do so in chapter 12, and the git repository is here : http://tddjs.com/code/12-abstracting-browser-differences-ajax.git

So the test code is :

TestCase("AppTest", {
    setUp: function() {
        tddjs.isLocal = stubFn(true);
        var ajax = tddjs.ajax;
        this.xhr = Object.create(fakeXMLHttpRequest);
        this.xhr.send=function () {
            this.readyState = 4;
            this.onreadystatechange();
        };
        this.xhr.responseText="<expected data>";
        ajax.create = stubFn(this.xhr);
    },


   "test article html ok": function () {
        var actualHtml = "";

        getHtml({ "title": "title", "header": "header", "text": "text", "authors": [ {"firstname": "firstname", "lastname": "lastname"} ] }, function(html) {
            actualHtml = html;
        });

        assertEquals("<expected html>", actualHtml);
   }
});

And the production code :

function getHtml(model, resultFunc) {
        tddjs.ajax.get("http://url/to/template", {
                success: function (xhr) {
                        resultFunc(mergeArticleModelAndView(model, xhr.responseText));
                }
        });
}

After having read jQuery code, I think it is possible to mock jQuery ajax request. Cf also jquery 1.5 mock ajax

Pleurisy answered 18/7, 2011 at 22:37 Comment(0)
P
3

Yes it is possible to do quite the same with jquery :

Test code (just the setup the test is the same) :

TestCase("AppTest", {
    setUp: function() {
        this.xhr = Object.create(fakeXMLHttpRequest);
        this.xhr.send=function () {
            this.readyState = 4;
        };
        this.xhr.getAllResponseHeaders=stubFn({});
        this.xhr.responseText="<expected data>";
        jQuery.ajaxSettings.isLocal=stubFn(true);
        jQuery.ajaxSettings.xhr=stubFn(this.xhr);
    },
// same test method

And the production code :

function getHtml(model, resultFunc) {
        $.get("/url/to/template", function(view) {
                resultFunc(mergeArticleModelAndView(model, view));
        });
}

That's the solution that we have chosen.

Pleurisy answered 19/7, 2011 at 20:34 Comment(0)
P
0

With sinon.js it is even simpler :

TestCase("AppTest", {
setUp: function() {
    this.server = sinon.fakeServer.create();
},
tearDown: function() {
    this.server.restore();
},

"test article html ok": function () {
    this.server.respondWith("mustache template");
    this.server.respond();
    var actualHtml = "";

    getHtml({ "title": "title", "header": "header", "text": "text", "authors": [ {"firstname": "firstname", "lastname": "lastname"} ] }, function(html) {
        actualHtml = html;
    });

    assertEquals("<expected html>", actualHtml);
}
Pleurisy answered 28/1, 2014 at 14:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.