使用 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("タイトルは必ず入力してください。");
});

考试的执行结果是通过。

testOK.png

我将开始编写一个测试,用相同的方法输入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文字以内で入力してください。");
});

考试的执行结果为“通过”。

testOkBoth.png
bannerAds