【Jest】anchor タグを押してwindow.locationが更新されるかテストするときに発生した console.error の対応方法

anchor タグを押して任意のURLに window.location が更新されるかテストを行った時、ナビゲーション周りのconsole.error がでてハマったので備忘録を残しておきます。

## 使用技術

- Next.js: v.14.2.14
- React: v.18.2.0
- Jest: v.29.7.0
- @testing-library/user-event: v.13.2.1
- testing-library/react: v.14.0.0 

## コード

今回のテスト対象コードとテストコードは以下です。

### テスト対象コード

テスト対象コードは以下です。href を受け取って Button Componentに渡し、Buttonが押された時hard navigationで画面遷移します。

```react

const Component = (href: string) => {

  const handleClick = () => {
    if (href) {
          window.location.href = href
    }
    console.log("Next!")
  }
  
  return(
  <div>
    <Button type="anchor" href={href} onClick={handleClick}>Topへ</Button>
  </div>)
}

```

### テストコード
このComponentに対するテストコードは以下です。ボタンを押した時、PCとSPのときそれぞれのURLに遷移するかをテストします。

```typescript
test.each([
    {
      device: 'Pc',
      url: '/top',
    },
    {
      device: 'Sp',
      url: '/sp/top',
    },
  ])(
    '$device表示時、Topへボタンをクリックすると$urlに遷移する',
    async ({  url }) => {
      const { findByRole } = render(<Compenent href={url} />)

      const anchor = await findByRole('link', { name: 'Topへ' })

      userEvent.click(anchor)

      await waitFor(() => {
        expect(location.href).toBe(url)
      })
    }
  )
```

## 発生したエラー

このテストを実行した結果以下エラーが発生しました。(テスト自体は通る)

エラー内容を見てみると、どうやらナビゲーションが実装されてない?旨が書いてありそうです。

```
console.error
   Error: Not implemented: navigation (except hash changes)
    at module.exports (/***/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
       at navigateFetch (/****/node_modules/jsdom/lib/jsdom/living/window/navigation.js:77:3)
       at exports.navigate (/***/node_modules/jsdom/lib/jsdom/living/window/navigation.js:55:3)
       at Timeout._onTimeout (/***/node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)
       at listOnTimeout (node:internal/timers:569:17)
       at processTimers (node:internal/timers:512:7) {
     type: 'not implemented'
   }
 
     at VirtualConsole.<anonymous> (node_modules/jest-environment-jsdom/build/index.js:63:23)
     at module.exports (node_modules/jsdom/lib/jsdom/browser/not-implemented.js:12:26)
     at navigateFetch (node_modules/jsdom/lib/jsdom/living/window/navigation.js:77:3)
     at exports.navigate (node_modules/jsdom/lib/jsdom/living/window/navigation.js:55:3)
     at Timeout._onTimeout (node_modules/jsdom/lib/jsdom/living/nodes/HTMLHyperlinkElementUtils-impl.js:81:7)
```

## 原因

[こちらの記事](https://github.com/jsdom/jsdom/issues/2112#issuecomment-359297866)によると原因は以下にありそうでした。

- JSDOMはナビゲーションをサポートしてない。
- ナビゲーションをサポートしていないので、anchor タグでページ遷移処理を行おうとすると、遷移が行えないエラーが表示されてしまう。
    - ただしanchor タグを押した時、hrefを元にwindow.locationオブジェクトの更新はできるので、アサーションの比較は成功しテスト自体は通る。

## 対応

以下のようにテスト内で、anchor タグのページ遷移処理を無効にすることでナビゲーションをサポートしていない旨のエラーをなくすことができました。

```typescript

beforeAll(() => {
  const defaultLocation = window.location
  Object.defineProperty(window, 'location', {
    writable: true,
    value: {
      ...defaultLocation,
    },
  })
})

afterAll(() => {
  Object.defineProperty(window, 'location', {
    writable: false,
  })
})

test.each([
    {
      device: 'Pc',
      url: '/top',
    },
    {
      device: 'Sp',
      url: '/sp/top',
    },
  ])(
    '$device表示時、Topへボタンをクリックすると$urlに遷移する',
    async ({  url }) => {
      const { findByRole } = render(<Compenent href={url} />)

      const anchor = await findByRole('link', { name: 'Topへ' })

	  //New: .preventDefault()でページ遷移無効
	  anchor.addEventListener('click', (event) => event.preventDefault(), false)
      userEvent.click(anchor)

      await waitFor(() => {
        expect(location.href).toBe(url)
      })
    }
  )
```

## おまけ

ほかにもこのエラーの対応方法はありそうなのでいくつか例をあげてみました。

### 1.今回のエラーだけ console.error をspyする

今回のナビゲーションのエラー限定で console.error を spy する方法

```typescript
const defaultErrorLog = console.error

const notImplementedErrorType =
  'Not implemented: navigation (except hash changes)'

jest.spyOn(console, 'error').mockImplementation((error) => {
  if (error.message === notImplementedErrorType) {
    return
  }
  defaultErrorLog(error)
})
```

### 2.anchorタグの href に指定されているURLの比較を行う

今回のテストは実際に画面遷移したかはスコープではありません。

なので anchorタグの href に指定されているURLを比較すれば、テストの要件を満たすことができてそうでした。


## 参考文献

https://github.com/jsdom/jsdom/issues/2112#issuecomment-359297866

https://developer.mozilla.org/ja/docs/Web/API/Location/href