Uenishi.Web

大阪に生息しているプログラマーのブログ

MSWで地味にハマったメモ

MSWのv2の導入でハマったことのメモ。

環境

 "react": "^18.2.0",
 "vite": "^4.3.2"
 "msw": "^2.3.0",

バックエンドのAPIがまだ開発されていない段階で、フロントエンドのUIを作成することになったため、効率的に開発を進めるためにMSWを導入した。

以下のような形で handler.tsbrowser.ts を用意し、main.tsxにセットアップ

handler.ts

import { http, HttpResponse, type ResponseResolver } from 'msw'

const mockTest: ResponseResolver = () => {
  return HttpResponse.json(
    {
      text: "test"
    }
  )
}

export const handlers = [
  http.get(`http://localhost:8000/test`, mockTest),
  http.get(`http://localhost:8000/ping`, mockTest),

browser.ts

import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'

export async function setupMsw() {
  if (import.meta.env.DEV && import.meta.env.VITE_USE_MSW === 'true') {
    const worker = setupWorker(...handlers)
    worker.start()
  }
}

main.tsx

import { createRoot } from "react-dom/client";
import React from "react";
import { Routes } from "@generouted/react-router";
import { setupMsw } from "./mocks/browser";

// VITE_USE_MSWがtrueまたはdevで実行時のみ、MSWが起動
setupMsw()

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const container = document.getElementById("root")!;

createRoot(container).render(
    <React.StrictMode>
        <Routes />
    </React.StrictMode>
);

何がおこったか
import React, { useEffect } from "react";

function TestIndexPage() {
      useEffect(() => {
        (async () => {

        const fet = await fetch("http://localhost:8000/test")
        const res = await fet.json()
        console.log("fet", res)
        const ping = await fetch("http://localhost:8000/ping")
        const pingres = await ping.json()
        console.log("pingres", pingres)
        })()
      }, [])
    return (
        <div>
          <p>test</p>
        </div>
    );
}

export default TestIndexPage;

Testページで上記のようにfetchすると、/testは404になるが、/pingはMSWでセットしたレスポンスが返るようになっていた。

結論

MSWのworker.setup() は非同期処理のため、setupが終了する前にレンダリングが実行され、初回のリクエスト時 (/testのfetch時) はまだworkerが動いていなかったため。

こんな感じで、遅れて「Mocking Enabled」が表示される

画像が読み込まれない場合はページを更新してみてください。

以下のように、browser.tsとmain.tsxでの記述を、worker.start() の非同期処理終了後にレンダリングするようにして解決。

browser.ts

import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'

export async function setupMsw() {
  if (import.meta.env.DEV && import.meta.env.VITE_USE_MSW === 'true') {
    const worker = setupWorker(...handlers)
    await worker.start()
  }
}

main.tsx

import { createRoot } from "react-dom/client";
import React from "react";
import { Routes } from "@generouted/react-router";
import { setupMsw } from "./mocks/browser";



// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const container = document.getElementById("root")!;

// VITE_USE_MSWがtrueまたはdevで実行時のみ、MSWが起動
setupMsw().then(() => {
		createRoot(container).render(
		    <React.StrictMode>
		        <Routes />
		    </React.StrictMode>
			);
})

無駄にハマったので自戒としてメモ…

参考