web-dev-qa-db-ja.com

jest.jsによるナビゲーション変更のインターセプト(またはlocation.hrefのオーバーライドと復元の方法)

アプリケーションコードが_location.href = "some-url"_を呼び出しています。ナビゲーションリダイレクトが発生したことを確認するテストを記述したいと思います。

Jsdomでjestを使用して、jest mock関数を使用してlocation.hrefセッターをオーバーライドしてそれを実行しようとしましたが、機能しています。

しかし、今はテストのクリーンアップで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である理由

ナビゲーションイベントをインターセプトしてテストするより良い方法はありますか?

ありがとう

13
avivr

新しい場所の文字列をlocation.hrefに割り当てる代わりに、 location.assign() メソッドを使用してください。次に、問題なくモックしてテストできます。

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

QuotesBroがすでに説明したように 彼の答え なので、むしろ 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')
  })
})
0
KevinH