Is it possible to mock the window.location object for a qUnit test?
Asked Answered
A

2

18

Let's say I have a utility function that, for simplicity's sake (the real thing is complicated and irrelevant), returns the current window's querystring.

var someUtilityFunction = () {
    return window.location.search.substring(1);
};

Now I want to unit test this function in qUnit (not sure if the testing harness is relevant or not):

test('#1 someUtilityFunction works', function () {
    // setup
    var oldQS = window.location.search;
    window.location.search = '?key1=value1&key2=value2&key3=value3';

    var expectedOutput = 'key1=value1&key2=value2&key3=value3';

    // test
    equals(someUtilityFunction(),
        expectedOutput,
        'someUtilityFunction works as expected.');

    // teardown
    window.location.search = oldQS;
});

The problem here is that setting the window.location.search to a different querystring is causing the page to reload, essentially entering an infinite request loop. Is there any way to mock out the window.location object without making any changes to the someUtilityFunction function?

Afroamerican answered 9/8, 2012 at 20:32 Comment(0)
M
19

We run into the same problem a few days ago. Mainly there are 2 approaches:

Rewrite your code

This might not be the best (if any) solution, but consider passing the window object to your function to make mocking easier. Even better, use a closure and encapsulate your code. This has a few more advantages:

  • You can shadow global vars
  • You can use private local vars
  • You can avoid naming collisions
  • The shadowing makes mocking really easy, just pass in something else

Wrap your code

You can wrap all your code inside a function which mocks the window object into a local variable. You have basically two possibilities there as well:

Suppose this is the mock:

var customWindow = {
    location: {
        search: "",
        hash: ""
    }
};

Use a closure

var someUtilityFunction;

(function(window) {
    // window is now shadowed by your local variable
    someUtilityFunction = () {
        return window.location.search.substring(1);
    };
})(customWindow);

This shadows the global window with a local window.

Use the with statement

Although I am usually strongly against with, it could really solve a lot of problems here. Since it basically remaps your scope, you can very easily mock your environment.

// first some more preparation for our mock
customWindow.window = customWindow;

with(customWindow) {

    // this still creates the var in the global scope
    var someUtilityFunction = () {
        // window is a property of customWindow
        return window.location.search.substring(1);
    };

    // since customWindow is our scope now
    // this will work also
    someUtilityFunction = () {
        // location is a property of customWindow too
        return location.search.substring(1);
    };

}

By the way: I don't know if the search property suffers from the same symptoms as the hash property - namely sometimes including the question mark and sometimes not. But you might want to consider using

window.location.search.replace(/^\?/, "");

instead of

window.location.substr(1);
Macroscopic answered 9/8, 2012 at 20:36 Comment(2)
thanks for the answer. rewriting the code would not help as it takes the window object optionally - this case is testing when one is not passed. wrapping the function in a "mock block" would be changing the code in the function, which it looks like i may have to do.Afroamerican
You somehow need to wrap your code in order to avoid rewriting your code. I just tried a with wrapper which works well too (however wrong it feels using it). You can then make your custom window object globally available to mock your properties and have it available within your code and also from the test as well. I suppose that's how jQuery and Zepto do it as well since they pass window and document to their wrappers too.Macroscopic
T
3

I've had some success using window.history.pushState. See this StackOverflow answer. For each unit test, I call a function setQueryString('var=something') which I then implement like this:

function setQueryString(queryString) {
  window.history.pushState({}, '', '?' + queryString);
}

You'll need to clear the query string with the afterEach method of QUnit.module, otherwise you're query string will be set to the value of the final test, and you'll get weird results.

Tasse answered 9/7, 2015 at 12:19 Comment(1)
Does not work for me. if I do a window.history.pushState(...); and check it directly with console.log(window.location) it gives me the current URL of the Qunit-html page. Do you have an Example code?Guileless

© 2022 - 2024 — McMap. All rights reserved.