Search code examples
jqueryajaxasynchronousqunitmockjax

Multiple responses from identical calls in asynch QUnit + Mockjax tests


I'm trying to test some jQuery ajax code using QUnit and Mockjax and have it return different JSON for different tests, like this:

$(document).ready(function() {

function functionToTest() {
    return $.getJSON('/echo/json/', {
        json: JSON.stringify({
            "won't": "run"
        })
    });
}

module("first");

test("first test", function() {

    stop();

    $.mockjax({
        url: '/echo/json/',
        responseText: JSON.stringify({
            hello: 'HEYO!'
        })
    });

    functionToTest().done(function(json) {
        ok(true, json.hello);
        start();
    });               
});


 test("second test", function() {

    stop();

    $.mockjax({
        url: '/echo/json/',
        responseText: JSON.stringify({
            hello: 'HELL NO!'
        })
    });


    functionToTest().done(function(json) {
        ok(true, json.hello);
        start();
    });


});

});

Unfortunately it returns the same response for each call, and order can't be guaranteed, so was wondering how I could set it up so that it was coupled to the actual request and came up with this:

$.mockjax({
    url: '/echo/json/',
    response: function(settings) {

        if (JSON.parse(settings.data.json).order === 1) {
            this.responseText = JSON.stringify({
                hello: 'HEYO!'
            });
        } else {
            this.responseText = JSON.stringify({
                hello: 'HELL NO!'
            });
        }
    }
});

This relies on parameters being sent to the server, but what about requests without parameters, where I still need to test different responses? Is there a way to use QUnit's setup/teardown to do this?


Solution

  • It looks like you need to call $.mockjaxClear(); before you create another mock handler.

    Mockjax works by changing the $.ajax method.

    At the bottom of its source code we can see that the $.mockjax method you are using, which is one of its exposed methods, just appends more handlers to the mockHandlers array.

        $.mockjax = function(settings) {
            var i = mockHandlers.length;
            mockHandlers[i] = settings;
            return i;
        };
    

    In the source code for the $.ajax replacement, we can see:

        // Iterate over our mock handlers (in registration order) until we find
        // one that is willing to intercept the request
        for(var k = 0; k < mockHandlers.length; k++) {
    

    Your problem is due to the $.ajax method being satisfied with the first handler (therefore not the latest) mock handler in the array for the /echo/json/ url.

    Here is my fork of your fiddle, just adding a $.mockjaxClear() line.


    Edit:

    A more flexible solution:

    • Outside any test function, decare a variable which will have the ID of the request handler for later changes.
    • Before a module where you need to override a certain handler, declare a variable (no need for initial value) for holding a backup of the handler you want to modify.
    • Then in the module setup function use $.extend to copy the settings from $.mockjax.handler(<your handlerID variable>) to this backup. In the module's teardown, use $.mockjaxClear(<your handlerID variable>) to delete the module, and then set <your handlerID variable> to something else.
    • Now you can override your handlers in the module setup and in the individual test functions.

    But don't take my word for it. Check out the fiddle.

    It's a lot more flexible like this. You can change that one handler and leave all the other handlers you might have intact.

    It does seem to be potentially error-prone, so I would advise to be careful.