使用jest.js拦截导航更改(或如何覆盖和恢复location.href)

时间:2017-09-12 06:54:56

标签: javascript unit-testing testing jestjs

应用程序代码正在调用location.href = "some-url"。 我想编写一个测试来验证导航重定向是否已经发生。

在jsdom上使用jest,我尝试使用jest mock函数重写location.href setter并且它正在工作。

但是现在我似乎无法在测试清理时恢复location.href属性,并且它在“location.href”上继续传输的其余测试失败。

it('test navigation happened', () => {
  const originalLocationHref = Object.getOwnPropertyDescriptor(window.location, 'href'); // returns undefined

  spyFn = jest.fn();
  Object.defineProperty(window.location, 'href', {
    set: spyFn,
    enumerable: true,
    configurable: true
  });

  someAppCodeThatShouldRedirectToSomeUrl();

  expect(spyFn).toBeCalledWith('http://some-url'); // this is working

  // Cleanup code, not working, because originalLocationHref is undefined
  Object.defineProperty(window.location, 'href', originalLocationHref);  
});

我错过了什么?为什么Object.getOwnPropertyDescriptor(window.location, 'href');undefined

是否有更好的方法来拦截导航事件以进行测试?

由于

3 个答案:

答案 0 :(得分:14)

使用location.assign()方法代替将新的位置字符串分配给location.href。然后你可以毫无问题地模拟和测试它:

it('test navigation happened', () => {
  window.location.assign = jest.fn();

  // here you call location.assign('http://some-url');
  redirectToSomeUrl();

  expect(window.location.assign).toBeCalledWith('http://some-url');

  // location.href hasn't changed because location.assign was mocked
});

答案 1 :(得分:11)

较新的jest / jsdom版本不允许再设置window.location.assign。可以这样修复:

delete window.location;
window.location = { assign: jest.fn() };

请注意,这会从window.location中删除所有其他对象,您可能需要根据测试和应用程序代码来模拟更多的对象。

来源:https://remarkablemark.org/blog/2018/11/17/mock-window-location/

答案 2 :(得分:5)

正如quotesBro已解释in his answer一样,您应该使用location.assign()

但是由于Jest v25(使用较新版本的JSDOM),您将收到以下错误:

TypeError: Cannot assign to read only property 'assign' of object '[object Location]'

这不是一个Jest / JSDOM错误。这是正常的浏览器行为,并且JSDOM会尝试像真实的浏览器一样工作。

一种解决方法是删除位置对象,创建自己的位置对象,并在运行测试后将其重置为原始位置对象:

describe('My awesome unit test', () => {
  // we need to save the original object for later to not affect tests from other files
  const realLocation = window.location

  beforeAll(() => {
    delete window.location
    window.location = { assign: jest.fn() }
    // or even like this if you are also using other location properties (or if Typescript complains):
    // window.location = { ...realLocation, assign: jest.fn() }
  })

  afterAll(() => {
    window.location = realLocation
  })

  it('should call location.assign', () => {    
    // ...your test code

    expect(window.location.assign).toHaveBeenCalled()

    // or even better:
    // expect(window.location.assign).toHaveBeenCalledWith('/my_link')
  })
})