如何使用Jest + Vuejs模拟window.location.href?

179

目前,我正在为我的项目实施单元测试,其中有一个包含window.location.href的文件。

我想要模拟这个进行测试,下面是我的示例代码:

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);
    });

但是我收到了这个错误:

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

我该如何解决它?


1
这个问题已经在另一个帖子中得到了回答这是答案链接 - Mustafa J
4
虽然不完全符合您的要求,但 window.location.assign(url) 函数实际上可以达到同样的效果,因此您可以使用 jest.spyOn(window.location, 'assign').mockImplementation(() => {}); 进行模拟。 - Brady Dowling
这个回答能解决你的问题吗?如何使用Jest模拟JavaScript中的'window'对象? - Michael Freidgeim
22个回答

196

你可以尝试:

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

针对该问题,请查看Jest Issue:
Jest Issue


13
对我来说使用 global 无效,我去掉了global. 并且还需要添加 writable: true,否则一旦设置后,其他的测试就不能更改它。 - xbmono
2
类型错误:位置不可重写。 - Royer Adames

117

2020 更新


基础

URL 对象Location 对象具有很多相同的功能。换句话说,它包含了像 pathnamesearchhostname 等属性。因此在大多数情况下,您可以采用以下方式:

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

高级

你还可以模拟可能需要的 Location 方法,这些方法在 URL 接口中不存在:

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

36
使用 TypeScript 更新后,delete window.location 将会触发错误:The operand of a 'delete' operator must be optional - dshun
7
如果必要的话,在删除 window.location 的那一行上方添加 //@ts-ignore。请注意不改变原意,语言通俗易懂。 - Tom
14
Reflect.deleteProperty(global.window, 'location') 可以处理这个问题而不会出现 TypeScript 错误。 - JCQuintas
9
为了解决TS错误,您可以执行delete (window as any).location;。这将删除全局窗口对象上的“location”属性。 - MkMan
2
为什么应该删除 window.location?难道只有通过 window.location = location 覆盖它吗? - Mohammad Kermani
显示剩余5条评论

48

我通过添加 writable: true 并将其移动到 beforeEach 来解决了这个问题。

这是我的示例代码:

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

2
这个解决方案同样适用于在Jest测试中覆盖window.location.hostname。我需要使用writable: true才能多次更改hostname - Jay
这对我非常有帮助 - Md. Amanullah
这就是我一直在寻找的。对我来说最好的解决方案。 - Enes Kirimi

25

GitHub上2019年的解决方案

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

1
这是唯一一个似乎对我有用并且提供了有帮助的原因的方法! :) - peter.swallow
它可以工作,但会破坏使用postMessage的后续测试,需要原始的 location.host 属性。 - Washington Guedes

21

最好的方法可能是创建一个新的URL实例,这样它就像location.href一样解析您的字符串,并更新location的所有属性,如.hash.search.protocol等。

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


14

2020年使用@testing-library/react进行示例演示,用于window.location.assign的方法:

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

3
如果不设置 writable: true,我的单元测试无法正常工作,因为后续的测试无法将其覆盖为其他内容。谢谢。 - xbmono

12

提供的示例中有许多并未模拟原始 Location 对象的属性。

我的做法是仅通过 URL 替换 Location 对象 (window.location),因为 URL 包含与 Location 对象相同的属性,如 "href"、 "search"、 "hash"、 "host"。

设置器和获取器的工作方式也与 Location 对象完全相同。

例如:

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);

        // ...
    });
});

1
我在想是否应该这样写:const realLocation = Object.assign({}, window.location);,因为我觉得直接赋值会传递一个引用,而后面可能会被覆盖。你有什么想法吗? - wlh
这段代码不会改变原始的window.location指向的对象 -- window.location属性内部设置了不同的对象。由于原始window对象没有被改变,因此无需克隆它。 - RobW
如果你想使用TS,就不应该滥用"@ts-ignore",这通常是一种不好的做法。 - Thomas Soos

5

2022年的TS

const orgLocation: Location = window.location;

beforeEach(() => {
  delete (window as any).location;
  window.location = (new URL("http://localhost") as any);
  window.location.assign = ({configurable:true, value:jest.fn()} as any);
  window.location.replace = ({configurable:true, value:jest.fn()} as any);
  window.location.reload = ({configurable:true, value:jest.fn()} as any);
});

afterAll(() => window.location = orgLocation);

4

您可以尝试使用 jest-location-mock

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

更新 jest 的配置文件,在 jest.config.js 文件或 package.json 文件中的 jest 属性中进行:

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

创建 jest-setup.js

import "jest-location-mock";

使用:

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

4

将@jabacchetta的解决方案扩展以避免此设置泄漏到其他测试中:

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;
  });
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接