React Router v7でルートモジュールのloaderやactionをテストする
loader や action が必要なルートモジュールコンポーネント自体のテストはReact Router v7のルートコンポーネントをテストするに書いた。
APIクライアントの取得方法は loader の context 引数から受け取るのか、または loader 内部で useContext を使って受け取るのか迷っているが、今は比較してシンプルな loader の context 引数から受け取るようにした。
まず workers/app.ts で context を定義する。
declare global { interface CloudflareEnvironment extends Env {}}
declare module "react-router" { export interface AppLoadContext { cloudflare: { env: CloudflareEnvironment; ctx: ExecutionContext; }; walletClient: ReturnType<typeof createWalletClient>; }}
export default { fetch(request, env, ctx) { return requestHandler(request, { cloudflare: { env, ctx }, walletClient: createWalletClient("http://localhost:8080"), }); },} satisfies ExportedHandler<CloudflareEnvironment>;これで loader にAPIクライアントが渡ってくるので、素朴に使えばいい。
export async function loader({ context }: Route.LoaderArgs) { const { merchants } = await context.walletClient.createMerchant({ ... }); return { merchants };}loader のテストをするときは context を組み立てる。Testing | Connectをみると、Connectがモック作成用の関数を提供してくれているので利用する。ただし自作しても大したことはない。
import { WalletService } from "@/proto/gen/wallet/v1/wallet_pb.js";import { createClient, createRouterTransport } from "@connectrpc/connect";
const transport = createRouterTransport(({ service }) => { service(WalletService, { async createMerchant() { return { $typeName: "wallet.v1.CreateMerchantResponse", id: "123", } } })})
const defaultContext: AppLoadContext = { cloudflare: { env: { ... }, ctx: { waitUntil: () => {}, passThroughOnException: () => {}, props: {}, }, }, walletClient: createClient(WalletService, transport),};毎回これを書くのは面倒なので、ユーティリティ関数を用意しておくといい。
export function createMockContext( overrides: Partial<AppLoadContext>,): AppLoadContext { return { ...defaultContext, ...overrides };}ところで context の代わりに useContext フックも利用できる。こちらをモックしてもいいが、明示的に渡せるものは渡すといいと思う。