mocking window.location.href in Javascript

JavascriptMockingJasmine

Javascript Problem Overview


I have some unit tests for a function that makes use of the window.location.href -- not ideal I would far rather have passed this in but its not possible in the implementation. I'm just wondering if its possible to mock this value without actually causing my test runner page to actually go to the URL.

  window.location.href = "http://www.website.com?varName=foo";    
  expect(actions.paramToVar(test_Data)).toEqual("bar");	

I'm using jasmine for my unit testing framework.

Javascript Solutions


Solution 1 - Javascript

The best way to do this is to create a helper function somewhere and then mock that:

 var mynamespace = mynamespace || {};
    mynamespace.util = (function() {
      function getWindowLocationHRef() {
          return window.location.href;
      }
      return { 
        getWindowLocationHRef: getWindowLocationHRef
      }
    })();

Now instead of using window.location.href directly in your code simply use this instead. Then you can replace this method whenever you need to return a mocked value:

mynamespace.util.getWindowLocationHRef = function() {
  return "http://mockhost/mockingpath" 
};

If you want a specific part of the window location such as a query string parameter then create helper methods for that too and keep the parsing out of your main code. Some frameworks such as jasmine have test spies that can not only mock the function to return desired values, but can also verified it was called:

spyOn(mynamespace.util, 'getQueryStringParameterByName').andReturn("desc");
//...
expect(mynamespace.util.getQueryStringParameterByName).toHaveBeenCalledWith("sort");

Solution 2 - Javascript

I would propose two solutions which have already been hinted at in previous posts here:

  • Create a function around the access, use that in your production code, and stub this with Jasmine in your tests:

    var actions = { getCurrentURL: function () { return window.location.href; }, paramToVar: function (testData) { ... var url = getCurrentURL(); ... } }; // Test var urlSpy = spyOn(actions, "getCurrentURL").andReturn("http://my/fake?param";); expect(actions.paramToVar(test_Data)).toEqual("bar");

  • Use a dependency injection and inject a fake in your test:

    var _actions = function (window) { return { paramToVar: function (testData) { ... var url = window.location.href; ... } }; }; var actions = _actions(window); // Test var fakeWindow = { location: { href: "http://my/fake?param" } }; var fakeActions = _actions(fakeWindow); expect(fakeActions.paramToVar(test_Data)).toEqual("bar");

Solution 3 - Javascript

You need to simulate local context and create your own version of window and window.location objects

var localContext = {
    "window":{
        location:{
            href: "http://www.website.com?varName=foo"
        }
    }
}

// simulated context
with(localContext){
    console.log(window.location.href);
    // http://www.website.com?varName=foo
}

//actual context
console.log(window.location.href);
// http://www.actual.page.url/...

If you use with then all variables (including window!) will firstly be looked from the context object and if not present then from the actual context.

Solution 4 - Javascript

Sometimes you may have a library that modifies window.location and you want to allow for it to function normally but also be tested. If this is the case, you can use a closure to pass your desired reference to your library such as this.

/* in mylib.js */
(function(view){
    view.location.href = "foo";
}(self || window));

Then in your test, before including your library, you can redefine self globally, and the library will use the mock self as the view.

var self = {
   location: { href: location.href }
};

In your library, you can also do something like the following, so you may redefine self at any point in the test:

/* in mylib.js */
var mylib = (function(href) {
    function go ( href ) {
       var view = self || window;
       view.location.href = href;
    }
    return {go: go}
}());

In most if not all modern browsers, self is already a reference to window by default. In platforms that implement the Worker API, within a Worker self is a reference to the global scope. In node.js both self and window are not defined, so if you want you can also do this:

self || window || global

This may change if node.js really does implement the Worker API.

Solution 5 - Javascript

Below is the approach I have take to mock window.location.href and/or anything else which maybe on a global object.

First, rather than accessing it directly, encapsulate it in a module where the object is kept with a getter and setter. Below is my example. I am using require, but that is not necessary here.

define(["exports"], function(exports){

  var win = window;

  exports.getWindow = function(){
    return win;
  };

  exports.setWindow = function(x){
    win = x;
  }

});

Now, where you have normally done in your code something like window.location.href, now you would do something like:

var window = global_window.getWindow();
var hrefString = window.location.href;

Finally the setup is complete and you can test your code by replacing the window object with a fake object you want to be in its place instead.

fakeWindow = {
  location: {
    href: "http://google.com?x=y"
  }
}
w = require("helpers/global_window");
w.setWindow(fakeWindow);

This would change the win variable in the window module. It was originally set to the global window object, but it is not set to the fake window object you put in. So now after you replaced it, the code will get your fake window object and its fake href you had put it.

Solution 6 - Javascript

This works for me:

delete window.location;
window.location = Object.create(window);
window.location.href = 'my-url';

Solution 7 - Javascript

This is similar to cpimhoff's suggestion, but it uses dependency injection in Angular instead. I figured I would add this in case someone else comes here looking for an Angular solution.

In the module, probably the app.module add a window provider like this:

@NgModule({
...
  providers: [
    {
      provide: Window,
      useValue: window,
    },
  ],
...
})   

Then in your component that makes use of window, inject window in the constructor.

constructor(private window: Window)

Now instead of using window directly, use the component property when making use of window.

this.window.location.href = url

With that in place you can set the provider in Jasmine tests using TestBed.

    beforeEach(async () => {
      await TestBed.configureTestingModule({
        providers: [
          {
            provide: Window,
            useValue: {location: {href: ''}},
          },
        ],
      }).compileComponents();
    });

Solution 8 - Javascript

IMO, this solution is a small improvement of cburgmer's in that it allows you to replace window.location.href with $window.location.href in the source. Granted I'm using Karma and not Jasmine, but I believe this approach would work with either. And I've added a dependency on sinon.

First a service / singleton:

function setHref(theHref) {
    window.location.href = theHref;
}
function getHref(theHref) {
    return window.location.href;
}

var $$window = {
        location: {
            setHref: setHref,
            getHref: getHref,
            get href() {
                return this.getHref();
            },
            set href(v) {
                this.setHref(v);
            }
        }
    };
function windowInjectable() {  return $$window; }

Now I can set location.href in code by injecting windowInjectable() as $window like this:

function($window) {
  $window.location.href = "http://www.website.com?varName=foo";
}

and mocking it out in a unit test it looks like:

sinon.stub($window.location, 'setHref');  // this prevents the true window.location.href from being hit.
expect($window.location.setHref.args[0][0]).to.contain('varName=foo');
$window.location.setHref.restore();

The getter / setter syntax goes back to IE 9, and is otherwise widely supported according to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set

Solution 9 - Javascript

Here's my generic solution that requires an extra import in production code, but doesn't require dependency injection or writing individual wrapper functions like getHref().

Basically we toss the window into a separate file and then our prod code imports the window indirectly from that file.

In production, windowProxy === window. In tests we can mutate the module which exports windowProxy and mock it with a new temporary value.

// windowProxy.js

/*
 * This file exists solely as proxied reference to the window object
 * so you can mock the window object during unit tests.
 */
export default window;
// prod/someCode.js
import windowProxy from 'path/to/windowProxy.js';

export function changeUrl() {
  windowProxy.location.href = 'https://coolsite.com';
}
// tests/someCode.spec.js
import { changeUrl } from '../prod/someCode.js';
import * as windowProxy from '../prod/path/to/windowProxy.js';

describe('changeUrl', () => {
  let mockWindow;
  beforeEach(() => {
    mockWindow = {};
    windowProxy.default = myMockWindow;
  });

  afterEach(() => {
    windowProxy.default = window;
  });

  it('changes the url', () => {
    changeUrl();
    expect(mockWindow.location.href).toEqual('https://coolsite.com');
  });
});

Solution 10 - Javascript

You need to fake window.location.href while being on the same page. In my case, this snipped worked perfectly:

$window.history.push(null, null, 'http://server/#/YOUR_ROUTE');
$location.$$absUrl = $window.location.href;
$location.replace();

// now, $location.path() will return YOUR_ROUTE even if there's no such route

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionwmitchellView Question on Stackoverflow
Solution 1 - JavascriptKurt HarrigerView Answer on Stackoverflow
Solution 2 - JavascriptcburgmerView Answer on Stackoverflow
Solution 3 - JavascriptAndrisView Answer on Stackoverflow
Solution 4 - JavascriptJim IsaacsView Answer on Stackoverflow
Solution 5 - Javascriptuser566245View Answer on Stackoverflow
Solution 6 - JavascriptAngularBoyView Answer on Stackoverflow
Solution 7 - JavascriptjakeView Answer on Stackoverflow
Solution 8 - JavascriptTongfaView Answer on Stackoverflow
Solution 9 - JavascriptcpimhoffView Answer on Stackoverflow
Solution 10 - JavascriptYoussef GamilView Answer on Stackoverflow