开发者问题收集

如何使用 jest 和 react 测试库测试调用提交表单的按钮

2021-02-08
35415

因此,我尝试测试单击按钮时 onSubmit 函数是否会被触发 - 我这样做的方式是通过测试 onSubmit 函数的内部是否被调用(axios post 方法)

测试

describe('RecipeSearch', () => {
    test('submit button should return post function to recipes/search/', () => {
        let mock = new MockAdapter(axios);
        userEvent.selectOptions(screen.getByRole('combobox'), 'Sweet');
        userEvent.click(screen.getByText('Search'));

        const config = {
            headers: {
                'Content-Type': 'application/json',
            },
        };
        const searchRecipes = mock.onPost(
            `${process.env.REACT_APP_API_URL}/recipes/search/`,
            { flavor_type: 'Sweet' },
            { config }
        );
        expect(searchRecipes).toHaveBeenCalled();
    });
});

错误

    expect(received).toHaveBeenCalled()

    Matcher error: received value must be a mock or spy function

    Received has type:  object
    Received has value: {"abortRequest": [Function abortRequest], "abortRequestOnce": [Function abortRequestOnce], "networkError": [Function networkError], "networkErrorOnce": [Function networkErrorOnce], "passThrough": [Function passThrough], "reply": [Function reply], "replyOnce": [Function replyOnce], "timeout": [Function timeout], "timeoutOnce": [Function timeoutOnce]}

函数

const recipeSearch = (props) => {
    const [formData, setFormData] = useState({
        flavor_type: 'Sour',
    });

    const { flavor_type } = formData;

    const [loading, setLoading] = useState(false);

    const onChange = (e) => setFormData({ ...formData, [e.target.name]: e.target.value });

    const onSubmit = (e) => {
        e.preventDefault();

        const config = {
            headers: {
                'Content-Type': 'application/json',
            },
        };

        setLoading(true);
        axios
            .post(
                `${process.env.REACT_APP_API_URL}/recipes/search/`,
                {
                    flavor_type,
                },
                config
            )
            .then((res) => {
                setLoading(false);
                props.setRecipes(res.data);
                window.scrollTo(0, 0);
            })
            .catch((err) => {
                setLoading(false);
                window.scrollTo(0, 0);
            });
    };

    return (
        <form  onSubmit={(e) => onSubmit(e)}>
            <div>
                <div>
                    <div>
                        <label htmlFor='flavor_type'>Choose Flavor</label>
                        <select
                            name='flavor_type'
                            onChange={(e) => onChange(e)}
                            value={flavor_type}
                        >
                            <option value='Sour'>Sour</option>
                            <option>Sweet</option>
                            <option>Salty</option>
                        </select>
                    </div>

                    <div>
                            <button type='submit'>Search</button> 
                    </div>
                </div>
            </div>
        </form>
    );
};

我已经添加了整个测试和组件代码,因此帮助会更容易。 提前致谢

(添加了 onChange + onSubmit 函数)

3个回答

创建 onSubmit 模拟并将其作为 prop 传递是行不通的,因为 onSubmit 回调是组件内部的,而不是 prop - 您无法从测试中访问它。

与其测试 onSubmit 是否已被调用,不如测试触发提交事件的结果。在本例中,这可能意味着验证是否发出了 axios 请求。

有关如何在测试中模拟 axios 的示例,请参阅 如何在 Jest 中测试 axios?

juliomalves
2021-02-20

我个人不喜欢仅仅为了测试目的而通过传递模拟或间谍函数来更改表单组件代码的想法。

对于我的表单,我想出了这个想法 -

  • 基本上我创建了 handleOnSubmitMock 函数,然后将其分配给 screen.getByRole("form", { name: "signup-form" }).onsubmit GlobalEventHandler。 (未将模拟函数传递给表单)
  • 然后我检查了 expect(handleOnSubmitMock).toHaveBeenCalled()expect(handleOnSubmitMock).not.toHaveBeenCalled() 是否通过。
  • 请注意 ,表单数据验证需要使用 HTML required 属性、 regex 模式和 onChange 处理程序进行,以防止使用无效数据提交表单。
    import React from "react";
    import { render, screen, fireEvent } from "@testing-library/react";
    import SignupForm from "../components/SignupForm";

    describe("SignupForm Component", () => {
      // Helper function to render the component
      const renderComponent = () => {
        return render(<SignupForm />);
      };
      const handleOnSubmitMock = jest.fn();

      it("does not submit an empty form", () => {
        renderComponent();
        screen.getByRole("form", { name: "signup-form" }).onsubmit =
          handleOnSubmitMock;

        // Submit the empty form
        fireEvent.click(screen.getByRole("button", { name: "Sign Up" }));

        // Expectations for form submission
        expect(handleOnSubmitMock).not.toHaveBeenCalled();
      });

      it("does not submit the form with an invalid username", () => {
        // complete the test case similarly
      });

      it("does not submit the form with an invalid email", () => {
        renderComponent();
        screen.getByRole("form", { name: "signup-form" }).onsubmit =
          handleOnSubmitMock;

        fireEvent.change(screen.getByPlaceholderText("Username"), {
          target: { value: "validUsername" },
        });
        fireEvent.change(screen.getByPlaceholderText("Email"), {
          target: { value: "[email protected]" },
        });
        fireEvent.change(screen.getByPlaceholderText("Password"), {
          target: { value: "ValidPassword1!" },
        });
        fireEvent.change(screen.getByPlaceholderText("Confirm Password"), {
          target: { value: "ValidPassword1!" },
        });

        // Submit the form
        fireEvent.click(screen.getByRole("button", { name: "Sign Up" }));

        // Expectations for form submission
        expect(handleOnSubmitMock).not.toHaveBeenCalled();
      });

      it("does not submit the form with an invalid password", () => {
        // complete the test case similarly
      });

      it("does not submit the form without matching passwords", () => {
        // complete the test case similarly
      });

      it("submits the form only with valid data", () => {
        renderComponent();
        screen.getByRole("form", { name: "signup-form" }).onsubmit =
          handleOnSubmitMock;

        // Fill in the form fields with valid data
        fireEvent.change(screen.getByPlaceholderText("Username"), {
          target: { value: "validUsername" },
        });
        fireEvent.change(screen.getByPlaceholderText("Email"), {
          target: { value: "[email protected]" },
        });
        fireEvent.change(screen.getByPlaceholderText("Password"), {
          target: { value: "ValidPassword1!" },
        });
        fireEvent.change(screen.getByPlaceholderText("Confirm Password"), {
          target: { value: "ValidPassword1!" },
        });

        // Submit the form
        fireEvent.click(screen.getByRole("button", { name: "Sign Up" }));

        // Expectations for form submission
        expect(handleOnSubmitMock).toHaveBeenCalled();
      });
    });

并且我的表单组件未接受任何 props -

    import React, { useState } from "react";

    interface FormData {
      username: string;
      email: string;
      password: string;
      confirmPassword: string;
    }

    const SignupForm: React.FC = () => {
      const [formData, setFormData] = useState<FormData>({
        username: "",
        email: "",
        password: "",
        confirmPassword: "",
      });
      ... ...

      const handleOnSubmit = (e: React.FormEvent): void => {
        ... ...
      };

      return (
        <div className="bg-gray-900 h-screen flex flex-col items-center justify-center">
          <img ... ... />
          <div className="bg-white p-8 rounded-lg shadow-md w-96">
            <h2 className="text-2xl font-semibold mb-4 text-center text-gray-800">
              Register
            </h2>
            <form aria-label="signup-form" onSubmit={handleOnSubmit}>
              <div className="mb-4">
                <input
                  id="username"
                  type="text"
                  name="username"
                  placeholder="Username"
                  required
                />
              </div>

              // other input fields ... ...

              <button
                type="submit">
                Sign Up
              </button>
            </form>
            <div className="mt-4 text-center">
              <p className="text-gray-600">
                Already have an account?{" "}
                <a href="/signin" className="text-blue-500 hover:underline">
                  Sign in
                </a>
              </p>
            </div>
          </div>
        </div>
      );
    };

    export default SignupForm;

您应该能够为您的 RecipeSearchForm 编辑上述示例,并且它应该可以工作。

Mehedi
2023-09-04

您是否尝试过通过文本选择按钮:

 describe('RecipeSearch', () => {
    test('test clicking the button triggers the onSubmit function', () => {
        const onSubmit = jest.fn();
        render(<RecipeSearch onSubmit={onSubmit} />);
        userEvent.selectOptions(screen.getByRole('combobox'), 'Sour');
        userEvent.click(screen.getByText('Search'));
        expect(onSubmit).toHaveBeenCalled();
    });
});

我不确定 getByRole 在您第一次尝试时如何处理第二个参数,但 getByText 应该可以工作。

Florian Motteau
2021-02-09