Jest: window.location.href can't be changed in tests.

Created on 13 Apr 2016  ·  70Comments  ·  Source: facebook/jest

Hi @cpojer,

This is actually more of a jsdom@8 issue...see tmpvar/jsdom#1388, but I want to pin here as well so Jest picks up whatever solution jsdom comes up with.

Previously with [email protected]/[email protected] you could write a test like this:

jest.autoMockOff()
jest.setMock('../lib/window', window)

jest.mock('cookies-js')
jest.mock('query-string')
jest.mock('superagent')

describe(['@utils/auth - When an AJAX response returns:'
].join('')
, function () {

  beforeEach(function () {
    window.location.href = 'http://quux.default.com'
    var queryString = require('query-string')
    queryString.__setMockParseReturns([{
      'access_token': '1234foo',
      'expires_in': '9999'
    }])
  })

  it(['should set a redirect token and goto platform ',
    'when the AJAX request returns 401.'
  ].join('')
  , function () {
    var superagent = require('superagent')
    superagent.__setMockAjaxResponses([
      [null, { 'status': 401 }]
    ])

    var href = window.location.href
    var auth = require('../index.js')
    auth.login(function (res) {})
    var Cookies = require('cookies-js')
    var config = require.requireActual('../config')
    expect(decodeURIComponent(window.location.href)).toBe([
      config.loginUrl,
      config.loginServiceLogin,
      '?client_id=',
      config.clientId,
      '&response_type=token',
      '&redirect_uri=',
      config.clientRedirectUri
    ].join(''))
    expect(Cookies.__getMockCookieData()[config.clientId + '_state_locationAfterLogin']).toBe(escape(href))
  })

And that test would pass. Since jsdom@8 this is no longer possible and these tests fail.

Seems like jsdom is looking at some type of capability, just wanted to make sure that Jest will pick up that capability when it is available.

Most helpful comment

You are right, this is indeed a jsdom issue. At Facebook, what we have done to work around this, is use this:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

this works for us, however we are still on jsdom 7 internally.

I'll close this, as I believe the Object.defineProperty way of doing things is fine. If that doesn't work for you in jsdom 8, I'm happy to reopen it.

All 70 comments

You are right, this is indeed a jsdom issue. At Facebook, what we have done to work around this, is use this:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

this works for us, however we are still on jsdom 7 internally.

I'll close this, as I believe the Object.defineProperty way of doing things is fine. If that doesn't work for you in jsdom 8, I'm happy to reopen it.

Cool, thanks I will try this out.

@cpojer, I can't seem to figure out what I need to click on to reopen this issue...

Is there anyway in jest environment for one to call jsdom.changeUrl(window, url) as described here https://github.com/tmpvar/jsdom#changing-the-url-of-an-existing-jsdom-window-instance in [email protected]?

old ticket but for those still having this issue we've started using window.location.assign() instead so in our tests we can mock the assign function like so..

it('will redirect with a bad route', () => {
    window.location.assign = jest.fn();
    const redirection = shallow(<Redirection />, {
      context: {
        router: {
          location: {
            pathname: '/wubbalubbadubdub',
          },
        },
      },
    });
    expect(window.location.assign).toBeCalledWith(`${CONFIG.APP_LEGACY_ROOT}`);
  });

Thanks @th3fallen . That's cool!

Btw @cpojer I start at FB on 1May.... ;P

Nice!

I'm trying to migrate our tests from Mocha+Chai+Sinon.js to Jest and can't figure out how to change location for a particular test.
Jest 19.x uses JSDom 9.12 that does not allow to change location using Object.defineProperty trick. Also, I can't use jsdom.changeURL() because of the reasons described in tmpvar/jsdom#1700.
@cpojer what about implementing some proxy method to jsdom.changeURL() in Jest?

@okovpashko we're planning to expose jsdom to the environment: https://github.com/facebook/jest/issues/2460

Object.defineProperty works for us at FB.

@thymikee I saw that issue but thought that the proposition was rejected.
@cpojer I misread your example and mixed it up with other related to this problem, where people suggested to use Object.defineProperty(window, 'location', {value: 'url'});. Thank you!

I need to change not only the href, so I wrote simple method, that may be useful for someone who will read this thread:

const setURL = (url) => {
  const parser = document.createElement('a');
  parser.href = url;
  ['href', 'protocol', 'host', 'hostname', 'origin', 'port', 'pathname', 'search', 'hash'].forEach(prop => {
    Object.defineProperty(window.location, prop, {
      value: parser[prop],
      writable: true,
    });
  });
};

Apologies for dragging out this thread further, but I have tried mocking out push function as suggested...

reactRouterReduxMock.push = (url) => {
   Object.defineProperty(window.location, 'href', {
    writable: true,
    value: url
       })
})

but I'm still getting a jsdom error that I can't seem to get round:

       TypeError: Cannot read property '_location' of null
       at Window.location (/Users/user/projects/app/client/node_modules/jsdom/lib/jsdom/browser/Window.js:148:79)
       at value (/Users/user/projects/app/client/test/integration-tests/initialSetup.js:122:32) //this is the defineProperty line above

I realise this is a jsdom error, but for those who have solved this is there any more setup context you could share that might let me get round this?

Thanks

@matt-dalton try my suggestion in https://github.com/facebook/jest/issues/890#issuecomment-295939071 works well for me

@matt-dalton what's your URL? do you have testURL set in your jest-config.json or does it initialize as about:blank?

@ianlyons Yeah I set value of "https://test.com/" for this in the package.json, and none of the paths are showing up as blank.

@th3fallen If I understand you correctly, I don't think this works for my use case. Are you passing the url as a context value that causes assign to be triggered? I am trying to put together a rudimentary integration test, so I want to check how the router responds to the initial data load. I have mocked the API response, and then need the URL change to take place using the app logic (i.e. I do not want to trigger it externally myself).

Object.defineProperty seems to do the trick for testing functionality that relies on window.location.search, for instance. That being said it mutates window.location.search so other tests may be impacted. Is there a way to "undo" the changes you've made on window.location.search via Object.defineProperty, kinda like jest mock functions have the mockReset function?

@msholty-fd you could try this approach:

const origLocation = document.location.href;
let location = origLocation;

beforeAll(() => {
  const parser = document.createElement('a');
  ['href', 'protocol', 'host', 'hostname', 'origin', 'port', 'pathname', 'search', 'hash'].forEach(prop => {
    Object.defineProperty(window.location, prop, {
      get: function() {
        parser.href = location;
        return parser[prop];
      }
    });
  });
});

afterEach(() => {
  location = origLocation;
});

test('location 1', () => {
  location = "https://www.google.com/";
  console.log(document.location.href); // https://www.google.com/
});

test('location 2', () => {
  console.log(document.location.href); // about:blank
});

It stopped working in Jest 22.0.1

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

Error message:

TypeError: Cannot redefine property: href
        at Function.defineProperty (<anonymous>)

Hmm, we might need to somehow allow people to call reconfigure. https://github.com/tmpvar/jsdom/blob/05a6deb6b91b4e02c53ce240116146e59f7e14d7/README.md#reconfiguring-the-jsdom-with-reconfiguresettings

Opened a new issue related to this, since this one was closed: #5124

@SimenB I'm not convinced that Jest should fix this. JSDOM should allow window.location.assign() to work as intended and reconfigure the output of window.location.href etc.

I got TypeError: Could not parse "/upgrades/userlogin?hardwareSku=sku1351000490stgvha" as a URL because jsdom has base url default to about:blank

I tried to assign a base url to jsdom, spent 4 hours on it without sucess (I know how to do it, just insert <base href='your_base_url' /> to the dom; but, the dom is created by jest , not by me, so i gave up.

the Object.defineProperty solution only works with old ver of jsdom (you get an 'cannot redefine property error with later version of jsdom);
if you are using jsdom ver > 10, as @th3fallen mentioned is the right solution.
use window.location.assign is the right way to go

If you just want some other url than about:blank, you can use testURL config.

thanks @SimenB for your reply.

No I was talking about base url not url. I have code that will do window.location.href="/login" and when running jest, jsdom throw exception complaining /login is not a valid url

TypeError: Could not parse "/login" as a URL

I checked the source code of jsdom and realised this is because I don't have a base url setup ( this is equivalent of typing "/login" in browser URL bar without a base address).

with jsdom, normally we can set up base url via

global.jsdom = new JSDOM('<html><head> <base href="base_url" /></head></html>')

but because jest set up jsdom , it is beyond our control.
--- update: I suppose I can explicitly add jsdom as dependency and configure jsdom manually. but I'm not sure if it's the recommended way to do it

I then found a solution which is to substitute window.location.href= with window.location.assign and mock assign function and it worked for me

@bochen2014 this issue has more information on how to use the newer version of jsdom: #5124

tl;dr: you can mock window.location.assign(), or you can use the jest-environment-jsdom-global, which will allow you to reconfigure jsdom in flight.

thanks @simon360

that's what I did ;-)
I used jsdom.reconfigure to setup different initial urls in my tests, and whenever I need to change url in code (not test), I use window.location.assign and mocked it. which worked for me.

just for people who may/will run into the same issue, to set the url for your jsdom

// jest.config.js
 module.exorts={ 
  testURL: 'http://localhost:3000',
  // or : 
  testEnvironmentOptions: {
     url: "http://localhost:3000/",
    referrer: "https://example.com/",
  }
}

note that this will set url for all your tests;
if you want a different url in some particular tests, use jsdom.reconfigure api;
if you need to change url on the fly outside of unit test code (i.e. production code), you need to use window.location.assign and mock it.

Posted it on other ticket, but I'll post it here:

Found nice solution for Jest 21.2.1

Ok, so far the easiest solution around this is:
Go into your Jest settings (for example I'll use package.json):

"jest": { "testURL": "http://localhost" }

Now you will have access to window object and then you can set URL to whatever you like during tests.

it('Should set href url to testURL', () => {
    // Here I set href to my needs, opinionated stuff bellow
    const newUrl = 'http://localhost/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
    Object.defineProperty(window.location, 'href', {
        writable: true,
        value: newUrl
    });

    console.log(window.location.href);
});

it('Should set pathname url to testURL', () => {
    // Here I set href to my needs, opinionated stuff bellow
    const newUrl = '/editor.html/content/raiweb/it/news/2018/02/altered-carbon-best-cyberpunk-tv-series-ever.html';
    Object.defineProperty(window.location, 'pathname', {
        writable: true,
        value: newUrl
    });

    console.log(window.location.pathname);
});

Hopefully this helps someone.

@petar-prog91 that was helpful. You have a typo though - it should be testURL not TestURL

@BarthesSimpson thanks for notice, updated comment.

Stop posting this, it does not work on jest": "^22.4.2"

Hi,
I have used this in the test, i delete the global state and create a new one with jsdom... :

   describe('componentDidMount', () => {
    delete global.window
    const window = (new JSDOM(``, {url: 'https://example.org/'})).window
    global.window = window
    describe('When window is defined', () => {
      const spy = jest.spyOn(Utils, 'extractTokenFromUrl')
      it('should call extract token function with window location', () => {
        mount(<Header />)
        expect(spy).toHaveBeenCalledWith('https://example.org/')
      })
    })
  })

@UserNT confirm — it gives TypeError: Cannot redefine property: href

@annemarie35 not works — ReferenceError: JSDOM is not defined

I don't know if this would help someone, but this is what I am currently doing.

const redirectTo = (url: string): void => {
  if (process.env.NODE_ENV === "test") {
    global.jsdom.reconfigure({ url: `${getBaseUrl()}${url}` });
  } else {
    window.location.replace(url);
  }
};

Write a redirect function and use that instead. So in testing env, it will rely on jsdom.reconfigure url to change url part.

I use it like this

export const clientFetchData = (
  history: Object,
  routes: Object,
  store: Object
) => {
  const callback = location =>
    match({ routes, location }, (error, redirectLocation, renderProps) => {
      if (error) {
        redirectTo("/500.html");
      } else if (redirectLocation) {
        redirectTo(redirectLocation.pathname + redirectLocation.search);
      } else if (renderProps) {
        if (!isEmpty(window.prerenderData)) {
          // Delete initial data so that subsequent data fetches can occur
          window.prerenderData = undefined;
        } else {
          // Fetch mandatory data dependencies for 2nd route change onwards
          trigger(
            FETCH_DATA_HOOK,
            renderProps.components,
            getDefaultParams(store, renderProps)
          );
        }

        trigger(
          UPDATE_HEADER_HOOK,
          renderProps.components,
          getDefaultParams(store, renderProps)
        );
      } else {
        redirectTo("/404.html");
      }
    });

  history.listen(callback);
  callback(history.getCurrentLocation());
};

After that, in your test, it can be sth like this

    describe("# match route", () => {
      it("should navigate to error page", () => {
        fetchData.clientFetchData(history, components, store);
        reactRouter.match.mock.calls[0][1](true);
        expect(window.location.href).toEqual(`${SERVER_URL}/500.html`);
      });

      it("should redirect to /hello-world.html page", () => {
        fetchData.clientFetchData(history, components, store);
        reactRouter.match.mock.calls[0][1](undefined, {
          pathname: "/hello-world.html",
          search: ""
        });
        expect(window.location.href).toEqual(`${SERVER_URL}/hello-world.html`);
      });
...

I ended up doing this which worked:

global.window = new jsdom.JSDOM('', {
  url: 'http://www.test.com/test?foo=1&bar=2&fizz=3'
}).window;

I have this at the top of my JSDOM setup file:

const { JSDOM } = require('jsdom');
const jsdom = new JSDOM('<!doctype html><html><body><div id="root"></div></body></html>', {
  url: "http://test.com"
});
const { window } = jsdom;

function copyProps(src, target) {
  const props = Object.getOwnPropertyNames(src)
    .filter(prop => typeof target[prop] === 'undefined')
    .map(prop => Object.getOwnPropertyDescriptor(src, prop));
  Object.defineProperties(target, props);
}

global.document = window.document;
global.window = window;
global.navigator = {
  userAgent: 'node.js',
};

global.HTMLElement = window.HTMLElement;

Fixed it by setting "testURL": "http://localhost/" in Jest config (I'm using latest version). By default it's "about:blank" and it was causing JSDOM error (you cannot change "about:blank" url to something else).

Resources:
http://jestjs.io/docs/en/configuration#testurl-string
https://github.com/jsdom/jsdom/issues/1372

I found this post to be very helpful: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"In your Jest configuration, make sure to set the following:

"testURL": "https://www.somthing.com/test.html"

Then in your beforeEach() section for your test, change the path as needed by using
history.pushState().

window.history.pushState({}, 'Test Title', '/test.html?query=true');

Voila! Now you change out your path for any test, without having to override any jsdom configurations as others suggest in the thread mentioned above. Not sure on which thread I found this solution on, but kuddos to the dev that posted it!"

@Mike-Tran You rock! That totally worked, so simple. I didn't even have to use the testURL setting.

@Mike-Tran That works! Thanks you! However, I didn't need the testURL or beforeEach. I just did:

window.history.pushState({}, 'Test Title', '/test.html?query=true');

And now I don't have to use Object.defineProperty anymore 😅

@jcmcneal thanks that did it for me! (jest 23.0.0)

If your goal is to mock the window object, here is my (not so elegant, but it works) solution:

Create an interface (not sure if interface is the right word, but I hope you get the point) class:

// window.js
export const { location } = window;

In your actual code, swap out window with the interface method's, e.g. win

// myFile.js
import * as win from './window';

export function doSomethingThatRedirectsPage() {
  win.location.href = 'google.com';
}

Then, in your jest tests, you just mock them out so jsdom doesn't complain. You can even assert them:

// myFile.test.js
import * as myFile from './myFile';
import * as win from './window';

it('should redirect', () => {
  win.location = { href: 'original-url' };

  expect(win.location.href).toBe('original-url');

  myFile.doSomethingThatRedirectsPage();

  expect(win.location.href).toBe('google.com');
});

@Mike-Tran , @jcmcneal thank! All works as aspected!

class SSOtestComponent extends React.Component {

componentDidMount() {
    let isSuccess = this.props.location.pathname === '/sso/test/success' ? true : false

    window.opener.postMessage({ type: "sso_test", isSuccess,...this.props.location.query}, window.location.origin)
}

onSsoAuthenticate() {

}

componentWillUnmount() {
}

render() {
    return (<Loader />);
}

}

module.exports = SSOtestComponent;

i am write the unit test case using enjyme and jest how will write the condition window.location ...pls give the answer

this worked for me

    const location = JSON.stringify(window.location);
    delete window.location;

    Object.defineProperty(window, 'location', {
      value: JSON.parse(location)
    });

    Object.defineProperty(global.location, 'href', {
      value: 'http://localhost/newURL',
      configurable: true
    });

on jest version 23.6.0

This worked for me.

delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }

@FelipeBohnertPaetzold thanks

Thanks @FelipeBohnertPaetzold. I was using location.host in my code, so found I needed a full location object, so the following worked better for me, rather than having to manually pass each location property:

delete global.window.location;
global.window.location = new URL("https://www.ediblecode.com/");

Note, this works in Node 6.13+ (see URL class docs) and I was using Jest 24.

Also note, this doesn't work with relative URLs, see https://url.spec.whatwg.org/#example-url-parsing.

This TypeScript is working for me on Jest 24.0.0 and Node 10.15.0:

src/setupTests.ts

import { mockWindow } from './testUtils';
mockWindow(window, 'http://localhost');

src/setupTests.test.ts

describe('setup tests', () => {

    describe('window.location', () => {
        const saveLocation = window.location;

        afterAll(() => {
            delete window.location;
            window.location = saveLocation;
        });

        it('location.assign assigns a location', () => {
            window.location.assign('http://foo.com');

            expect(window.location.href).toBe('http://foo.com/');

            (window.location.assign as jest.Mock<void, [string]>).mockClear();
        });

        it('location.replace replaces a location', () => {
            window.location.replace('http://bar.com');

            expect(window.location.href).toBe('http://bar.com/');

            (window.location.replace as jest.Mock<void, [string]>).mockClear();
        });

        it('location.reload is a spy', () => {
            window.location.reload();

            expect(window.location.reload).toHaveBeenCalledTimes(1);

            (window.location.reload as jest.Mock).mockClear();
        });
    });
});

src/testUtils.ts

interface MockedLocation extends Location {
    assign: jest.Mock<void, [string]>;
    reload: jest.Mock;
    replace: jest.Mock<void, [string]>;
}

interface MockedWindow extends Window {
    location: MockedLocation;
}

export function mockWindow(win: Window = window, href = win.location.href) {
    const locationMocks: Partial<MockedLocation> = {
        assign: jest.fn().mockImplementation(replaceLocation),
        reload: jest.fn(),
        replace: jest.fn().mockImplementation(replaceLocation),
    };

    return replaceLocation(href);

    function replaceLocation(url: string) {
        delete win.location;
        // tslint:disable-next-line:no-any
        win.location = Object.assign(new URL(url), locationMocks) as any;
        return win as MockedWindow;
    }
}

src/testUtils.test.ts

import { mockWindow } from './testUtils';

describe('test utils', () => {

    describe('mockWindow', () => {
        const saveLocation = window.location;

        afterAll(() => {
            delete window.location;
            window.location = saveLocation;
        });

        it('location.assign assigns a location', () => {
            const { assign } = mockWindow().location;
            assign('http://foo.com');

            expect(window.location.href).toBe('http://foo.com/');

            assign.mockClear();
        });

        it('location.replace replaces a location', () => {
            const { replace } = mockWindow().location;
            replace('http://bar.com');

            expect(window.location.href).toBe('http://bar.com/');

            replace.mockClear();
        });

        it('location.reload is a spy', () => {
            const { reload } = mockWindow().location;
            reload();

            expect(window.location.reload).toHaveBeenCalledTimes(1);

            reload.mockClear();
        });
    });
});

@jedmao

Hey, man) Great util!

For me tests in src/setupTests.test.ts a little bit redundant, cause you already completely tested mockWindow util in src/testUtils.test.ts. So, in tests for src/setupTests.ts it's enough to test, that you call mockWindow with correct arguments.

Thank you)

@tzvipm @j-u-p-iter thanks for the 👍. I just released @jedmao/storage and @jedmao/location, which are both completely agnostic of Jest. You should be able to spyOn the appropriate methods without writing any additional tests, as the npm packages come completely tested.

In case you're getting this error when using Vue, just use this.$router.push({...}) instead of this.$router.go({...})

image

Place the code below on line 1:
delete global.window.location;
global.window.location = "";

A click event that is changing the window.location can now be captured.

"jest": "^23.6.0"
v10.15.0
6.5.0

This works:

delete window.location;
window.location = Object.assign({}, window.location);
const url = Object.assign({}, new URL('http://google.com'));
Object.keys(url).forEach(prop => (window.location[prop] = url[prop]));

Or better yet...

delete (global as any).window;
(global as any).window = new JSDOM(undefined, { url: 'http://google.com' }).window;

You are right, this is indeed a jsdom issue. At Facebook, what we have done to work around this, is use this:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

this works for us, however we are still on jsdom 7 internally.

I'll close this, as I believe the Object.defineProperty way of doing things is fine. If that doesn't work for you in jsdom 8, I'm happy to reopen it.

Yeah, I have some functions deal with location.search and location.hash, and I want to test it with defineProperty as you mentioned. It won't work!

When I turn off jest silent mode, I found this: Error: Not implemented: navigation (except hash changes)

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Not implemented: navigation (except hash changes)
        at module.exports (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
        at navigateFetch (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:74:3)
        at exports.navigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:52:3)
        at LocationImpl._locationObjectNavigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:29:5)
        at LocationImpl.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:213:10)
        at Location.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/generated/Location.js:93:25)
        at Object.assign (/home/ghlandy/projects/wdph-utils/src/__tests__/url.test.js:6:14)
        at Object.asyncJestLifecycle (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37)
        at resolve (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>) undefined

And now I have no idea about how to test my funtions.

Anyone have any way to change the test url

You are right, this is indeed a jsdom issue. At Facebook, what we have done to work around this, is use this:

Object.defineProperty(window.location, 'href', {
  writable: true,
  value: 'some url'
});

this works for us, however we are still on jsdom 7 internally.
I'll close this, as I believe the Object.defineProperty way of doing things is fine. If that doesn't work for you in jsdom 8, I'm happy to reopen it.

Yeah, I have some functions deal with location.search and location.hash, and I want to test it with defineProperty as you mentioned. It won't work!

When I turn off jest silent mode, I found this: Error: Not implemented: navigation (except hash changes)

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Not implemented: navigation (except hash changes)
        at module.exports (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
        at navigateFetch (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:74:3)
        at exports.navigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/navigation.js:52:3)
        at LocationImpl._locationObjectNavigate (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:29:5)
        at LocationImpl.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/window/Location-impl.js:213:10)
        at Location.assign (/home/ghlandy/projects/wdph-utils/node_modules/jsdom/lib/jsdom/living/generated/Location.js:93:25)
        at Object.assign (/home/ghlandy/projects/wdph-utils/src/__tests__/url.test.js:6:14)
        at Object.asyncJestLifecycle (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:53:37)
        at resolve (/home/ghlandy/projects/wdph-utils/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>) undefined

And now I have no idea about how to test my funtions.

Anyone have any way to change the test url

And In my situation, have a field testURL in jest.config.js file may works. But what if I want change testURL before each test.

I found this post to be very helpful: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking

"In your Jest configuration, make sure to set the following:

"testURL": "https://www.somthing.com/test.html"

Then in your beforeEach() section for your test, change the path as needed by using
history.pushState().

window.history.pushState({}, 'Test Title', '/test.html?query=true');

Voila! Now you change out your path for any test, without having to override any jsdom configurations as others suggest in the thread mentioned above. Not sure on which thread I found this solution on, but kuddos to the dev that posted it!"


Excellent solution!!! Thank you very much! @Mike-Tran
I wanted a short and not invasive solution like this!

To get this working as of June 2019 I had to do this:

    delete global.window.location;
    global.window = Object.create(window);
    global.window.location = {
      port: '123',
      protocol: 'http:',
      hostname: 'localhost',
    };

I use this....

window.history.pushState({}, '', `${url}/`);

Probably part of my JSDOMTestWrapper can help somebody

    /** @type {Window} */
    this.testWindowObject = Object.create(window);
    const wnd = this.testWindowObject;
    this.testWindowObject.history = {
      state: null,
      prev: { /** @todo immutable stack with the go(step) method emulation */
        state: null,
        pathname: null,
        search: null,
      },
      go(step) {
        logger.special('history go called', step);
        logger.warn('history go has not supported yet');
      },
      back() {
        this.state = this.prev.state;
        wnd.location.pathname = this.prev.pathname;
        wnd.location.search = this.prev.search;
        const eventData = this.state ? { url: this.state.displayURL, newState: this.state, type: 'push' } : null;
        wnd.sm.eventsService.triggerEvent(ROUTER_EVENTS.ROUTE_PUSH, eventData);
        wnd.sm.urlService.simpleRouteTo(`${ wnd.location.pathname || '' }${ wnd.location.search || '' }`);
        logger.special('history back emulated');
      },
      pushState(state, title, url) {
        this.prev.state = Object.assign({}, this.state);
        this.prev.pathname = '' + wnd.location.pathname;
        this.prev.search = '' + wnd.location.search;
        this.state = state;
        if (title) wnd.document.title = title;
        const [p, s] = url.split('?');
        wnd.location.pathname = p;
        wnd.location.search = s ? `?${ s }` : '';
        logger.special('push state emulated', { state, title, url });
      },
      replaceState(state, title, url) {
        this.prev.state = Object.assign({}, this.state);
        this.prev.pathname = '' + wnd.location.pathname;
        this.prev.search = '' + wnd.location.search;
        this.state = state;
        if (title) wnd.document.title = title;
        const [p, s] = url.split('?');
        wnd.location.pathname = p;
        wnd.location.search = s ? `?${ s }` : '';
        logger.special('replace state emulated', { state, title, url });
        logger.special('test: urlService.getPathName()', wnd.sm.urlService.getPathName());
      },
    };
    this.testWindowObject.innerWidth = WND_WIDTH;
    this.testWindowObject.innerHeight = WND_HEIGHT;
    this.testWindowObject.fetch = fetchFn;
    this.testWindowObject.localStorage = lstMock;
    this.testWindowObject.scrollTo = (x, y) => {
      /** not implemented yet https://github.com/jsdom/jsdom/issues/1422 */
      if (typeof x !== 'number' && (x.left || x.top)) {
        y = x.top;
        x = x.left;
      }
      // logger.info(`window.scrollTo(${ x }, ${ y })`);
    };

    if (fetchFn === JSDOMTestWrapper.FETCH_FN.DEV_MOCK) {
      global.Request = RequestMock;
      this.testWindowObject.Request = RequestMock;
    }

    if (href) {
      this.testWindowObject.location = Object.assign({}, this.testWindowObject.location, urlapi.parse(href));
    }
    else {
      this.testWindowObject.location = Object.assign({}, this.testWindowObject.location);
    }

    (function(ELEMENT) {
      ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector;
      ELEMENT.closest = ELEMENT.closest || function closest(selector) {
          if (!this) return null;
          if (this.matches(selector)) return this;
          if (!this.parentElement) {return null}
          else return this.parentElement.closest(selector)
        };
      ELEMENT.getBoundingClientRect = ELEMENT.getBoundingClientRect || (() =>
        ({ bottom: WND_HEIGHT, height: WND_HEIGHT, left: 0, right: WND_WIDTH, top: 0, width: WND_WIDTH, x: 0, y: 0 }));
    }(Element.prototype));

    this.testWindowObject.getBoundingClientRect = () =>
      ({ bottom: WND_HEIGHT, height: WND_HEIGHT, left: 0, right: WND_WIDTH, top: 0, width: WND_WIDTH, x: 0, y: 0 });

    this.testWindowObject.__resizeListeners__ = [];
    this.testWindowObject.__resizeTriggers__ = {};
    this.testWindowObject._detectElementResize = {
      removeResizeListener: () => {},
    };

    this.testWindowObject.matchMedia = jest.fn().mockImplementation(query => {
      return {
        matches: false,
        media: query,
        onchange: null,
        addListener: jest.fn(),
        removeListener: jest.fn(),
      };
    });

    this.rftpr = () => {};
    this.mode = mode;
    this.renderFirstTimePromise = new Promise((resolve) => {
      this.rftpr = resolve;
    });

    this.marpr = () => {};
    this.mobileAppReadyPromise = new Promise((resolve) => {
      this.marpr = resolve;
    });

    if (mode === JSDOMTestWrapper.MODE.MOBILE_APP) {
      this.testWindowObject.navigator = Object.assign({}, this.testWindowObject.navigator, {
        language: storeKey,
        appVersion: '5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36',
        userAgent: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36',
        vendor: 'Google Inc.',
      });

      global.intercom = {
        registerUnidentifiedUser: jest.fn(),
        registerForPush: jest.fn(),
      };
    }

    const XApp = mode ? MobileApp : App;
    const app = <XApp window={ this.testWindowObject } rftResolve={ this.rftpr } storeKey={ storeKey } apiHost={ apiVersion } forceMobileDetection={ mode } />;
    render(app, this.testWindowObject.document.body);

    if (mode === JSDOMTestWrapper.MODE.MOBILE_APP) {
      setTimeout(() => {
        this.testWindowObject.sm.deviceService.appRestorePathHasInit = this.marpr;
        this.testWindowObject.sm.deviceService.fireEvent(this.testWindowObject.document, 'deviceready');
      }, 200);
    }

This approach works as of Sep 27, 2019: https://stackoverflow.com/a/54034379/1344144

global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, "location", {
    value: {
       href: url
    },
    writable: true
});

Another solution works for me currently without writing jsdom:

  1. Make sure you have set testURL in jest.config.js no matter the value:
// jest.config.js
'testURL': 'https://someurl.com'

In your test file:

window.history.pushState({}, 'Mocked page title', 'www.yoururl.com');

Learned from: https://www.ryandoll.com/post/2018/3/29/jest-and-url-mocking. Thansk to Ryan!

not working for me:

TypeError: Assignment to read-only properties not allowed in strict mode

image

it("should save hash when history is not found", () => {
    const historyBkp = global.window.history;

    delete global.window.history;
    global.window.history = false;

    externalLoader.savePageURL(urlTraining);

    expect(window.location.hash).to.be.equal(`#page=${urlTraining}`);

    global.window.history = historyBkp;
    window.location.hash = "";
});

not working for me:

TypeError: Assignment to read-only properties not allowed in strict mode

image

it("should save hash when history is not found", () => {
  const historyBkp = global.window.history;

  delete global.window.history;
  global.window.history = false;

  externalLoader.savePageURL(urlTraining);

  expect(window.location.hash).to.be.equal(`#page=${urlTraining}`);

  global.window.history = historyBkp;
  window.location.hash = "";
});

add this to the global file.

delete global.window.location;
global.window.location = "";

This approach works as of Sep 27, 2019: https://stackoverflow.com/a/54034379/1344144

global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, "location", {
    value: {
       href: url
    },
    writable: true
});

I'm trying something similar, with location.assign, but seems this isn't working anymore.

this works for me on jest 24.9.0

 window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');

this works for me on jest 24.9.0

 window.history.replaceState({}, 'Test Title', '/test?userName=James&userNumber=007');

I had to make code async in order to get this to work because I was running code inside a promise.

so is working now 😃

How to test chenge location in vuex action ?

async setForm({ rootState, state, commit, dispatch }, formData) {
          ....
          switch (answ.result.type) {
            ....
            case 'redirect':
              console.log(answ.data.url);
              window.location = answ.data.url;
              console.log({ location: window.location.href });
              break;
            default:
              break;
it('setForm - success, redirect', async done => {
      expect(window.location.href).toBe('https://www.google.ru/');

I have error:

    expect(received).toBe(expected) // Object.is equality

    Expected: "https://www.google.ru/"
    Received: "http://localhost/"

this worked for me

    const location = JSON.stringify(window.location);
    delete window.location;

    Object.defineProperty(window, 'location', {
      value: JSON.parse(location)
    });

    Object.defineProperty(global.location, 'href', {
      value: 'http://localhost/newURL',
      configurable: true
    });

on jest version 23.6.0

what's the global?

where's the global definition?

This worked for me.

delete global.window.location
global.window.location = { href: 'https://test-domain.com.br', ...anyOptions }

this creates a Location with all the original functionality, but it's mockable:

beforeAll(() => {
  const location = window.location
  delete global.window.location
  global.window.location = Object.assign({}, location)
})
Was this page helpful?
0 / 5 - 0 ratings