LoaderFunctionとActionFunctionをテストする
LoaderFunction
と ActionFunction
のテストを行いました。ここではVitestを使ってテストを行っています。テスト環境の構築については、以下の記事をご覧ください。
LoaderFunction のテスト
テストを行うにあたって、ソースを修正します。app/routes/index.tsx
を以下のように書き換えます。
import { useLoaderData } from "@remix-run/react";
import { json } from "@remix-run/node";
import type { LoaderArgs, LoaderFunction } from "@remix-run/node";
export const loader: LoaderFunction = async ({ request }: LoaderArgs) => {
return json({
email: "test@test.com",
password: "p@ssw0rd",
});
};
export default function Index() {
const loaderData = useLoaderData();
return (
<>
<div>{loaderData.email}</div>
<div>{loaderData.password}</div>
</>
);
}
loader()からの値を受け取ってそのまま表示しています。
npm run dev
を実行してhttp://localhost:3000
を表示すると以下のような画面が表示されます。
ではテストを書いていきます。tests
フォルダ配下にroutes/index.test.tsx
を作成します。
この loader()は固定値を返すだけなので、固定値が期待するものかどうかをテストします。
import { expect, test } from "vitest";
import { loader } from "~/routes";
test("loaderがemailとpasswordを返す", async () => {
const response = await loader({
request: new Request("http://localhost:3000"),
params: {},
context: {},
});
const loaderData = await response.json();
expect(loaderData).toStrictEqual({
email: "test@test.com",
password: "p@ssw0rd",
});
});
loader()の引数として request、params、context を渡しています。Request を作成して渡します。
loader()で return されたオブジェクトを toStrictEqual()で比較しています。toStrictEqual()は vitest のテスト用の関数で、値が受け取った値と等しいか、同じ構造を持っているか、それがオブジェクト (再帰的に比較) であり、同じ型であるかを判定します。
ActionFunction のテスト
app/routes/index.tsx
を以下のように書き換えます。入力フォームに文字を入力して送信するとinput-wordあり
が表示されます。文字を入力しないで送信するとinput-wordなし
が表示されます。
import { Form, useActionData, useLoaderData } from "@remix-run/react";
import { json } from "@remix-run/node";
import type {
ActionArgs,
ActionFunction,
LoaderArgs,
LoaderFunction,
} from "@remix-run/node";
export const loader: LoaderFunction = async ({ request }: LoaderArgs) => {
return json({
email: "test@test.com",
password: "p@ssw0rd",
});
};
export const action: ActionFunction = async ({ request }: ActionArgs) => {
const form = await request.formData();
const inputWord = form.get("input-word");
if (inputWord?.length === 0) {
return json({
word: "input-wordなし",
});
} else {
return json({
word: "input-wordあり",
});
}
};
export default function Index() {
const actionData = useActionData();
const loaderData = useLoaderData();
return (
<>
<div>{loaderData.email}</div>
<div>{loaderData.password}</div>
<div>
<Form method="post">
<input type="text" name="input-word" id="input-word" />
<button type="submit">送信</button>
</Form>
</div>
<div>{actionData?.word}</div>
</>
);
}
以下 2 点をテストします。
- 入力フォームに文字が入力されている場合は
input-wordあり
を返すこと - 入力フォームに文字が入力されている場合は
input-wordなし
を返すこと
テストを書いていきます。
import { expect, test } from "vitest";
import { action, loader } from "~/routes";
test("loaderのテスト", async () => {
const response = await loader({
request: new Request("http://localhost:3000"),
params: {},
context: {},
});
const loaderData = await response.json();
expect(loaderData).toStrictEqual({
email: "test@test.com",
password: "p@ssw0rd",
});
});
test("入力フォームが入力されていない場合、「input-wordなし」が返ってくる", async () => {
const body = new URLSearchParams({
"input-word": "",
});
const request = new Request("http://localhost:3000", {
method: "POST",
body: body,
});
const response = await action({
request: request,
params: {},
context: {},
});
const decoder = new TextDecoder();
const reader = response.body.getReader();
const { value } = await reader.read();
const actionData = JSON.parse(decoder.decode(value));
expect(response.status).toBe(200);
expect(actionData).toStrictEqual({
word: "input-wordなし",
});
reader.releaseLock();
});
test("入力フォームが入力されている場合、「input-wordあり」が返ってくる", async () => {
const body = new URLSearchParams({
"input-word": "test",
});
const request = new Request("http://localhost:3000", {
method: "POST",
body: body,
});
const response = await action({
request: request,
params: {},
context: {},
});
const decoder = new TextDecoder();
const reader = response.body.getReader();
const { value } = await reader.read();
const actionData = JSON.parse(decoder.decode(value));
expect(response.status).toBe(200);
expect(actionData).toStrictEqual({
word: "input-wordあり",
});
reader.releaseLock();
});
URLSearchParams を設定します。入力フォームの name をinput-word
に設定しているので、キーをinput-word
にして文字列を設定します。
Request を作成します。body には先ほど作成した URLSearchParams()を設定します。
action()の引数として request、params、context を渡しています。
返ってきた response の body はReadableStream
なので、デコードする必要があります。
getReader()でリーダーを取得します。するとReadableStreamDefaultReader
が取得できます。これをread()で読み取ります。Promise を返してくるので、await で待ちます。すると value と done が取得できます。value をみると Uint8Array オブジェクトになっているので、これをデコードして JSON にしています。
最後にreleaseLock()でストリームのロックを解除して終了です。
最後に
LoaderFunction
と ActionFunction
のテストを書いてきました。今までテスト書いてなかったので、どんどんテスト書いていこうと思います。
Discussion