使用 React-Hook-Form 和 zod 进行验证测试
我将测试React-Hook-Form创建的输入表单是否显示验证错误。
開發環境
节点:18.14.0
反应:18.2.0
类型脚本:5.1.6
快速:4.4.2
测试:29.6.1
测试库/反应:14.0.0
反应钩子表单:7.45.1
家谱:7.0.26
我使用 vite-cli 和 react-ts 模板创建了一个 react 环境。
代码 (daima)
规格
定义字段的最大和最小字符数。
-
- タイトル
必須 ( 最小 1 文字 )
最大 25 文字
实现输入表单
import { useForm } from "react-hook-form";
export const CreatePostForm: React.FC = () => {
const {
register,
formState: { errors },
} = useForm({
defaultValues: {
title: "",
},
});
return (
<div>
<form>
<input
type="text"
aria-label="タイトル"
aria-invalid={!!errors.title}
{...register("title")}
/>
// バリデーションエラー時に表示するエラーメッセージ
{errors.title && (
<p role="alert">
{errors.title?.message}
</p>
)}
</form>
</div>
);
};
在这种状态下,由于没有定义验证规则和错误消息,因此我们将根据上述规格使用zod进行验证的创建。
import z from "zod";
const schema = z
.object({
title: z
.string()
.min(1, "タイトルは必ず入力してください。")
.max(25, "タイトルは25文字以内で入力してください。")
})
.required()
.strict();
type Input = z.infer<typeof schema>;
将其添加到先前创建的输入表单中。
在useForm的选项中添加{mode: “onBlur”},以便在失去焦点时进行验证。
export const CreatePostForm: React.FC = () => {
const errorMessageId = useId();
const {
register,
formState: { errors },
} = useForm<Input>({
resolver: zodResolver(schema),
defaultValues: {
title: "",
},
mode: "onBlur",
});
return (
<div>
<form>
<label>
タイトル:
<input
type="text"
aria-label="タイトル"
aria-errormessage={errorMessageId}
aria-invalid={!!errors.title}
{...register("title")}
/>
{errors.title && (
<p role="alert" id={errorMessageId}>
{errors.title?.message}
</p>
)}
</label>
</form>
</div>
);
};
另外,通过在上述的input标签的aria-errormessage和p标签的id元素中包含生成的id,可以将input元素与错误消息元素关联起来,以便在测试代码中可以引用toHaveErrorMessage(errorMessage)。我们参考了这篇文章。
创作故事
使用 Storybook 创建故事。
输入命令 “npx storybook@latest init” 然后会问 “Would you like to install it?” 输入 “y” 并安装所需的库。
一旦所有安装都完成,Storybook 的准备工作就完成了。
import type { Meta, StoryObj } from "@storybook/react";
import { CreatePostForm } from "./CreatePostForm";
const meta: Meta<typeof CreatePostForm> = {
component: CreatePostForm,
};
export default meta;
type Story = StoryObj<typeof CreatePostForm>;
export const EmptyTitle: Story = {
play: async ({
canvasElement,
}: {
canvasElement: HTMLElement;
}): Promise<void> => {
const canvas = within(canvasElement);
// タイトル入力欄にフォーカスを当てる
await canvas.getByRole("textbox", { name: "タイトル" }).focus();
// 何も入力せずにフォーカスを外す
await userEvent.tab();
},
};
考试制作
由于要将Storybook用于测试,所以需要添加以下库:
$ yarn add -D @storybook/jest @storybook/testing-library
创建一个测试文件。
import { render, screen, waitFor } from "@testing-library/react";
import { composeStories } from "@storybook/react";
import * as stories from "./CreatePostForm.stories";
test("タイトルが入力されていない場合、エラーメッセージを表示する", async () => {
const { EmptyTitle } = composeStories(stories);
const { container } = render(<EmptyTitle />);
await EmptyTitle.play({ canvasElement: container });
const title = screen.getByRole("textbox", { name: "タイトル" });
await waitFor(() => {
expect(title).toBeInvalid();
});
expect(title).toHaveErrorMessage("タイトルは必ず入力してください。");
});
考试的执行结果是通过。

我将开始编写一个测试,用相同的方法输入26个字符。
export const OverTitleLength: Story = {
play: async ({
canvasElement,
}: {
canvasElement: HTMLElement;
}): Promise<void> => {
const canvas = within(canvasElement);
await userEvent.type(
canvas.getByRole("textbox", { name: "タイトル" }),
"A".repeat(26)
);
await userEvent.tab();
},
};
test("タイトルに26文字入力された場合、エラーメッセージを表示する", async () => {
const { OverTitleLength } = composeStories(stories);
const { container } = render(<OverTitleLength />);
await OverTitleLength.play({ canvasElement: container });
const title = screen.getByRole("textbox", { name: "タイトル" });
await waitFor(() => {
expect(title).toBeInvalid();
});
expect(title).toHaveErrorMessage("タイトルは25文字以内で入力してください。");
});
考试的执行结果为“通过”。
