Testing location change in JS

Every now and then I come across a scenario where I need to unit-test some code that navigates across pages by directly changing location. Some time ago I have accidentally stumbled upon a solution: the location.assign() function.

For example, if I have a button that first does some work and then navigates to some other page:

function handleClick() {
  saveTheDocument().then(function() {
    location = '/some/other/page';
  });
}

I can’t test this code because it will nevigate away from the test runner’s page, so I loose the whole thing! 8-|

How does location.assign() help? Well, if I change my code to use location.assign() instead of direct assignment, like this:

function handleClick() {
  saveTheDocument().then(function() {
    location.assign('/some/other/page');
  });
}

it works the same way, but I can now stub the location’s assign method in tests, like this:

describe('click handler', function() {
  beforeEach(function() {
    sinon.stub(location, 'assign');
  });

  it('navigates to the other page', function() {
    handleClick();

    expect(location.assign.calledOnce).toBe(true);
    expect(location.assign.lastCall.args[0]).toBe('/some/other/page');
  });

  afterEach(function() {
    location.assign.restore();
  });
});

I have packed the stubbing code into a stubLocation test helper, and now I only have to say this:

TestHelpers.stubLocation(beforeEach, afterEach);

describe('click handler', function() {
  it('navigates to the other page', function() {
    handleClick();

    expect(location.assign.calledOnce).toBe(true);
    expect(location.assign.lastCall.args[0]).toBe('/some/other/page');
  });
});

Neat! 8-)