ChatGPT解决这个技术问题 Extra ChatGPT

How to mock window.location.href with Jest + Vuejs?

Currently, I am implementing unit test for my project and there is a file that contained window.location.href.

I want to mock this to test and here is my sample code:

it("method A should work correctly", () => {
      const url = "http://dummy.com";
      Object.defineProperty(window.location, "href", {
        value: url,
        writable: true
      });
      const data = {
        id: "123",
        name: null
      };
      window.location.href = url;
      wrapper.vm.methodA(data);
      expect(window.location.href).toEqual(url);
    });

But I get this error:

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

I had tried some solutions but not resolve it. I need some hints to help me get out of this trouble. Plz help.

This has been answered in another thread Here is the link to the answer
Not exactly what you're looking for but window.location.assign(url) functionally does the same thing so you could mock that instead using jest.spyOn(window.location, 'assign').mockImplementation(() => {});

m
mava

You can try:

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

Have a look at the Jest Issue for that problem:
Jest Issue


For me using global didn't work, I removed global. and I also needed to add writable: true otherwise once set the other tests can't change it.
j
jabacchetta

2020 Update

Basic

The URL object has a lot of the same functionality as the Location object. In other words, it includes properties such as pathname, search, hostname, etc. So for most cases, you can do the following:

delete window.location
window.location = new URL('https://www.example.com')

Advanced

You can also mock Location methods that you might need, which don't exist on the URL interface:

const location = new URL('https://www.example.com')
location.assign = jest.fn()
location.replace = jest.fn()
location.reload = jest.fn()

delete window.location
window.location = location

with typescript update, delete window.location will trigger an error The operand of a 'delete' operator must be optional
@dhsun any solution to the problem you have mentioned above? I am facing similar issue
Add // @ts-ignore above the line delete window.location; if you must
Reflect.deleteProperty(global.window, 'location') deals with that without ts error
To get around that TS error you can do delete (window as any).location;
T
Tran Son Hoang

I have resolved this issue by adding writable: true and move it to beforeEach

Here is my sample code:

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

This solution also works for overwriting window.location.hostname in Jest tests. I needed writable: true in order to change the hostname more than once.
this helps me a lot
This is what I was looking for. Best solution for me.
s
serv-inc

Solution for 2019 from GitHub:

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


This is the only one that seems to work for me and with a helpful reason why! :)
It works, but breaks following tests using postMessage that needs the original location.host property
K
Kaiido

The best is probably to create a new URL instance, so that it parses your string like location.href does, and so it updates all the properties of location like .hash, .search, .protocol etc.

it("method A should work correctly", () => {
  const url = "http://dummy.com/";
  Object.defineProperty(window, "location", {
    value: new URL(url)
  } );

  window.location.href = url;
  expect(window.location.href).toEqual(url);

  window.location.href += "#bar"
  expect(window.location.hash).toEqual("#bar");
});

https://repl.it/repls/VoluminousHauntingFunctions


J
Juan Lago

Many of the examples provided doesn't mock the properties of the original Location object.

What I do is just replace Location object (window.location) by URL, because URL contains the same properties as Location object like "href", "search", "hash", "host".

Setters and Getters also work exactly like the Location object.

Example:

const realLocation = window.location;

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

    afterEach(() => {
        window.location = realLocation;
    });

    test('My test func', () => {

        // @ts-ignore
        delete window.location;

        // @ts-ignore
        window.location = new URL('http://google.com');

        console.log(window.location.href);

        // ...
    });
});

I wonder if it shouldn't be const realLocation = Object.assign({}, window.location); since I feel just assigning it directly would be passing a reference which is later overwritten. Thoughts?
The object that the original window.location points to is not mutated in this code -- the window.location property has different objects set inside it. Since there's no mutation of the original window object, there's no need to clone it.
D
Devin Clark

Working example with @testing-library/react in 2020 for window.location.assign:

  afterEach(cleanup)
  beforeEach(() => {
    Object.defineProperty(window, 'location', {
      writable: true,
      value: { assign: jest.fn() }
    })
  })

writable: true was necessary for my unit tests to work otherwise the subsequent tests wouldn't be able to overwrite it to something else. Thanks
t
thisismydesign

Extending @jabacchetta's solution to avoid this setting bleeding into other tests:

describe("Example", () => {
  let location;

  beforeEach(() => {
    const url = "https://example.com";
    location = window.location;
    const mockLocation = new URL(url);
    mockLocation.replace = jest.fn();
    delete window.location;
    window.location = mockLocation;
  });

  afterEach(() => {
    window.location = location;
  });
});

l
lequan

You can try a helper:

const setURL = url => global.jsdom.reconfigure({url});

describe('Test current location', () => {
  test('with GET parameter', () => {
    setURL('https://test.com?foo=bar');
    // ...your test here
  });
});

E
Estevão Lucas

This is valid for Jest + TypeScript + Next.js (in case you use useRoute().push

const oldWindowLocation = window.location;

beforeAll(() => {
  delete window.location;
  window.location = { ...oldWindowLocation, assign: jest.fn() };
});

afterAll(() => {
  window.location = oldWindowLocation;
});

E
Ed Lucas

JSDOM Version

Another method, using JSDOM, which will provide window.location.href and all of the other properties of window.location, (e.g. window.location.search to get query string parameters).

import { JSDOM } from 'jsdom';

...

const { window } = new JSDOM('', {
    url: 'https://localhost/?testParam=true'
});
delete global.window;
global.window = Object.create(window);

T
Tyler Caine Rhodes

How to reassign window.location in your code base; the simplest working setup we found for our Jest tests:

const realLocation = window.location;

beforeEach(() => {
  delete window.location;
});

afterEach(() => {
  window.location = realLocation;
});

M
Muhammed Moussa

you can try jest-location-mock.

npm install --save-dev jest-location-mock

update jest configs at jest.config.js file or jest prop inside package.json:

setupFilesAfterEnv: [ "./config/jest-setup.js" ]

create jest-setup.js

import "jest-location-mock";

usage:

it("should call assign with a relative url", () => {
    window.location.assign("/relative-url");
    expect(window.location).not.toBeAt("/");
    expect(window.location).toBeAt("/relative-url");
});

s
skanecode

Can rewrite window.location by delete this global in every test.

delete global.window.location;
const href = 'http://localhost:3000';
global.window.location = { href };

B
Bazze

Based on examples above and in other threads, here is a concrete example using jest that might help someone:

describe('Location tests', () => {
    const originalLocation = window.location;

    const mockWindowLocation = (newLocation) => {
        delete window.location;
        window.location = newLocation;
    };

    const setLocation = (path) =>
        mockWindowLocation(
            new URL(`https://example.com${path}`)
        );

    afterEach(() => {
        // Restore window.location to not destroy other tests
        mockWindowLocation(originalLocation);
    });

    it('should mock window.location successfully', () => {
        setLocation('/private-path');

        expect(window.location.href).toEqual(
            `https://example.com/private-path`
        );
    });
});

I
Imran Khan

Probably irrelevant. But for those seeking a solution for window.open('url', attribute) I applied this, with help of some comments above:

window = Object.create(window);
const url = 'https://www.9gag.com';
Object.defineProperty(window, 'open', { value: url });

expect(window.open).toEqual(url);

S
Sgnl

Here's a simple one you can use in a beforeEach or ala carte per test.

It utilizes the Javascript window.history and its pushState method to manipulate the URL.

window.history.pushState({}, 'Enter Page Title Here', '/test-page.html?query=value');