How do I unit test this JavaScript function, including mocking of Ajax call?
Asked Answered
S

2

7

As seen in this jsFiddle, I have a function 'init' that configures a button so that when clicked it opens a progress dialog and issues an Ajax call. I want to unit test this JavaScript code (using QUnit), and check the following cases:

  1. Ajax call succeeds
  2. Ajax call fails

I need to mock out at least the Ajax call and the call to window.open, and other calls I'm sure, depending on unit test implementation.

How can I write QUnit unit tests for my code that tests these two scenarios?

EDIT: The code that needs to be tested:

var statusmod = (function() {
    var spinner = $("#spinner");

    var init = function(id) {
        var progressDialog = $("#progressdialog-content").dialog({
            autoOpen: false,
            title: "Launching Status Page"
        });
        var errorDialog = $("#errordialog-content").dialog({
            autoOpen: false,
            modal: true,
            buttons: {
                "OK": function() {
                    $(this).dialog("close");
                }
            }
        });

        var btn = $("#button-status");
        btn.button().click(function() {
            spinner.show();

            progressDialog.dialog("open");

            var url = $.validator.format("/api/binid/?id={0}", id);
            // Call Web service to get binary ID
            $.ajax({
                url: url,
                dataType: "json"
            }).done(function(data) {
                window.open($.validator.format("http://status/?Page=Status&Id={0}", data.Id), target = "_newtab");
            }).fail(function(jqXHR, msg, errorThrown) {
                errorDialog.dialog("open");
            }).always(function() {
                progressDialog.dialog("close");
            });

            return false;
        });
    };

    return {
        init: init,
        _spinner: spinner
    };
}());
Scotfree answered 22/8, 2012 at 14:4 Comment(2)
Consider mockjax for mocking the ajax call, works like a charmIngeborg
@rsplak Could you please show how to run tests after Ajax callbacks have been invoked? Can't see how, at the moment.Scotfree
S
1

I have successfully written a QUnit test for the success case and another for the failure case, as you can see from this jsFiddle. I employed Mockjax to fake Ajax responses and simulate success/failure conditions. Notably, I configured Ajax calls to be synchronous, so that I could write synchronous tests, as I had trouble figuring out how to run my tests after asynchronous Ajax callbacks had fired.

I also make use of the Sinon.JS library to fake dependencies and verify that e.g. dialogs are launched correctly.

The working test code is included below, see my question for the function under test (statusmod.init). Let me know if there's something you think I've left out.

var dialogSpy = null;
var windowSpy = null;
var id = "srcId";
var binId = "binId";
var url = $.validator.format("/api/binid/?id={0}", id);
var btnId = "#button-status";

module("Open status page", {
    setup: function() {
        // Allow us to run tests synchronously
        $.ajaxSetup({
            async: false
        });
        windowSpy = sinon.spy(window, "open");
        dialogSpy = sinon.spy();
        sinon.stub($.fn, "dialog", function() {
            return {
                "dialog": dialogSpy
            };
        });

        statusmod.init(id);
    },
    teardown: function() {
        windowSpy.restore();
        $.fn.dialog.restore();
        $.mockjaxClear();
        // Remove click event handler for each test
        $(btnId).unbind();
    }
});

test("Successfully open status page", function() {
    expect(4);

    $.mockjax({
        url: url,
        contentType: "text/json",
        responseText: {
            Id: binId
        }
    });

    var spinner = statusmod._spinner;
    var spinnerSpy = sinon.spy(spinner, "show");

    $(btnId).click();

    ok(spinnerSpy.calledOnce, "Spinner shown");
    ok(dialogSpy.withArgs("open").calledOnce, "Dialog opened");
    ok(dialogSpy.withArgs("close").calledOnce, "Dialog closed");
    equal(windowSpy.lastCall.args[0], $.validator.format("http://status/?Page=Status&Id={0}", binId), "Window opened");
});

test("Binary ID not found on server", function() {
    expect(3);

    $.mockjax({
        url: url,
        contentType: "text/json",
        status: 404
    });

    $(btnId).click();

    ok(dialogSpy.withArgs("open").calledTwice, "Dialogs opened");
    ok(dialogSpy.withArgs("close").calledOnce, "Progress dialog closed");
    ok(!windowSpy.called, "Window not launched");
});
Scotfree answered 27/8, 2012 at 7:50 Comment(0)
I
0

First of all, download and include Mockjax.

Then mock your ajax calls:

module("Mock Ajax", {
  setup: function () {
    /** this mocks the ajax call **/
    $.mockjax({
      url: 'yourUrl.php',
      data: {'action': 'your',
        'data': {
          'that': 'you',
          'send': 'here'
        }
      },
      responseText:'{"your": ["returned":"json"],"string"}'
    });
  });
});

And then you can make your ajax call in a testcase:

test( "Your testcase", function() {
  $.ajax('yourUrl.php',
    data: {'action': 'your',
       'data': {
      'that': 'you',
      'send': 'here'
    }
  }, function(data) {
      ok("do something with your data");
  });
});

Et voilà, you've succesfully tested something! You can add extra parameters to your mockjax call (isTImeout, isError, etc). Documentation can be found here.

These are the basics, you can edit it to suit your needs using the documentation, which is pretty complete.

Ingeborg answered 23/8, 2012 at 17:8 Comment(2)
Thanks, but this doesn't really solve my problem. I don't want to make a mock Ajax call in my testcase, I want to call status.init, click the button, and ensure that the Ajax call gets made correctly and that the done/fail/always callbacks do the right thing. I have an almost working solution from hacking on the problem today, but am currently stuck at a tiny detail in mockjax.Scotfree
On that note, maybe you can answer my specific mockjax question as it should be quite simple? #12094231Scotfree

© 2022 - 2024 — McMap. All rights reserved.