【Jest】mswでuseSWRをモックする場合の設定

# 概要
下記記事を参考に、Mock Service WorkerでuseSWRのモックを作成していたが、そのままではうまく動かなかったため追加の設定方法をまとめ。


https://qiita.com/suzu1997/items/e4ee2fc1f52fbf505481#%EF%B8%8Euseswrcsr%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88

# 環境
- react 18.2.0
- next 13.1.6
- jest 29.6.2
- msw 1.2.3

# 関連ファイル
```js:front/app/pages/gadgets/index.js
import useSWR from 'swr'

export default function Gadgets() {
  const fetcher = (url) => fetch(url).then((r) => r.json())
  const { data, error, isLoading } = useSWR('http://localhost:3000/api/v1/gadgets', fetcher)

  if (error) return <div>エラーが発生しました。時間をおいて再度お試しください。</div>

  if (data || isLoading) {
    return (
      <div>
        <div>dataがあります</div>
        <div id='feed_gadget'>
          <div id='gadgets' className='gadgets'>
            {data?.gadgets?.map((gadget) => {
              return <div>{gadget.name}</div>
            })}
          </div>
        </div>
      </div>
    )
  }
}

```


```js:front/app/__tests__/gadgets/index.test.js
import '@testing-library/jest-dom'
import { cleanup, render, screen, waitFor } from '@testing-library/react'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import { SWRConfig } from 'swr'
import Gadgets from '../../pages/gadgets/index'

jest.mock('next/router', () => ({
  useRouter: jest.fn().mockReturnValue({
    push: jest.fn(),
    query: { message: ['initialMessage'], status: 'initialStatus' },
  }),
}))

const handlers = [
  rest.get('http://localhost:3000/api/v1/gadgets', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        gadgets: [
          {
            id: 1,
            user_id: 1,
            name: 'テストガジェット名',
            created_at: '2023/01/01',
            updated_at: '2023/02/01',
            user: {
              id: 1,
              name: 'testUserName1',
              image: { url: null },
            },
          },
        ],
      }),
    )
  }),
]

const server = setupServer(...handlers)
beforeAll(() => {
  server.listen()
})
afterEach(() => {
  server.resetHandlers()
  cleanup()
})
afterAll(() => {
  server.close()
})

describe('Gadgets', () => {
  test('ガジェット一覧が正常に表示される', async () => {
    render(
      <SWRConfig value={{ dedupingInterval: 0 }}>
        <Gadgets />
      </SWRConfig>,
    )

    await waitFor(() => {
      const dataElement = screen.getByText('テストガジェット名')
      expect(dataElement).toBeInTheDocument()
    })
  })
})

```

# 発生したエラー
```console
 FAIL  __tests__/gadgets/index.test.js (12.954 s)
  Gadgets
    ✕ ガジェット一覧が正常に表示される (1142 ms)

  ● Gadgets › ガジェット一覧が正常に表示される

    Unable to find an element with the text: テストガジェット名. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    Ignored nodes: comments, script, style
    <body>
      <div>
        <div>
          エラーが発生しました。時間をおいて再度お試しください。
        </div>
      </div>
    </body>

      75 |     )
      76 |
    > 77 |     await waitFor(() => {
         |                  ^
      78 |       const dataElement = screen.getByText('テストガジェット名')
      79 |       expect(dataElement).toBeInTheDocument()
      80 |     })

      at waitForWrapper (node_modules/@testing-library/dom/dist/wait-for.js:160:27)
      at Object.<anonymous> (__tests__/gadgets.test.js:77:18)
```
### エラー発生箇所
当エラーは、front/app/pages/gadgets/index.jsの下記useSWRのerrorを受け、
```js
const { data, error, isLoading } = useSWR('http://localhost:3000/api/v1/gadgets', fetcher)

```
同ファイルのエラーハンドリングにて表示されたものであることを確認。
```js
if (error) return <div>エラーが発生しました。時間をおいて再度お試しください。</div>
```

### エラー詳細
errorオブジェクトの中身を確認。
```diff_javascript:pages/gadgets/index.js
  if (error)
+    return (
+      <div>
+        <div>エラーが発生しました。時間をおいて再度お試しください。</div>
+        <div>{error.message}</div>
+      </div>
+    )
```

```console:error.message
fetch is not defined
```


テストコードにfetchのモックを追加。
- ライブラリを追加
`yarn add --dev jest-fetch-mock`

- テストコード追記
```diff_javascript:index.test.js
+ import { enableFetchMocks } from 'jest-fetch-mock' // jest-fetch-mockをインポート

+ enableFetchMocks() // jest-fetch-mockを有効にする
```

※下記のやり方では `Request is not defined`のエラーが発生するため注意

```diff_javascript:index.test.js
+ import fetch from 'node-fetch' *// node-fetchをインポート*
+ *global*.fetch = fetch *// fetchをモックする*
```

上記設定により、Jestのグローバルなfetchオブジェクトをモックすることで、useSWRのモックも正常に動作させることができた。

# 最終的なテストコード

```diff_javascript:front/app/__tests__/gadgets/index.test.js
import '@testing-library/jest-dom'
import { cleanup, render, screen, waitFor } from '@testing-library/react'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import { SWRConfig } from 'swr'
import Gadgets from '../../pages/gadgets/index'

// インポートを追加
+ import { enableFetchMocks } from 'jest-fetch-mock' // jest-fetch-mockをインポート
// モック
+ enableFetchMocks() // jest-fetch-mockを有効にする

jest.mock('next/router', () => ({
  useRouter: jest.fn().mockReturnValue({
    push: jest.fn(),
    query: { message: ['initialMessage'], status: 'initialStatus' },
  }),
}))

const handlers = [
  rest.get('http://localhost:3000/api/v1/gadgets', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        gadgets: [
          {
            id: 1,
            user_id: 1,
            name: 'テストガジェット名',
            created_at: '2023/01/01',
            updated_at: '2023/02/01',
            user: {
              id: 1,
              name: 'testUserName1',
              image: { url: null },
            },
          },
        ],
      }),
    )
  }),
]

const server = setupServer(...handlers)
beforeAll(() => {
  server.listen()
})
afterEach(() => {
  server.resetHandlers()
  cleanup()
})
afterAll(() => {
  server.close()
})

describe('Gadgets', () => {
  test('ガジェット一覧が正常に表示される', async () => {
    render(
      <SWRConfig value={{ dedupingInterval: 0 }}>
        <Gadgets />
      </SWRConfig>,
    )

    await waitFor(() => {
      const dataElement = screen.getByText('テストガジェット名')
      expect(dataElement).toBeInTheDocument()
    })
  })
})

```

 
# 最後に
より良い方法や間違い等ありましたらご指摘いただけますと幸いです！