Skip to content

React Router v7でルートモジュールのloaderやactionをテストする

loaderaction が必要なルートモジュールコンポーネント自体のテストはReact Router v7のルートコンポーネントをテストするに書いた。

APIクライアントの取得方法は loadercontext 引数から受け取るのか、または loader 内部で useContext を使って受け取るのか迷っているが、今は比較してシンプルな loadercontext 引数から受け取るようにした。

まず workers/app.tscontext を定義する。

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 フックも利用できる。こちらをモックしてもいいが、明示的に渡せるものは渡すといいと思う。