React 测试库看不到 useState 更新函数的结果

React Testing Library can't see result of useState update function

问题:

我有一个 React 组件,它有一个简单的表单和一个用于在提交时显示输出的区域。

我已经手动测试并确认提交表单会按预期输出值。但是 我无法让 React 测试库做出相同的断言

据我所知,似乎没有看到更新的页面内容。

这是测试失败的输出:

 ● User should be able to submit their first name

    expect(element).toHaveTextContent()

    Expected element to have text content:
      firstName: Matthew
    Received:
      firstName:

      47 |
      48 |     expect(output.firstName).toHaveTextContent("firstName:");
    > 49 |     expect(output.firstName).toHaveTextContent("firstName: Matthew");
         |                              ^
      50 | });
      51 |
      52 | /*

组件如下:

import React, { useState } from "react";
import './Playground.css';

const Playground = ({
    firstName,
    lastName,
    email,
    subject,
    message
}) => {

    let [myState, setMyState] = useState({
        firstName: firstName ? firstName : "",
        lastName: lastName ? lastName : "",
        email: email ? email : "",
        subject: subject ? subject : "",
        message: message ? message : ""
    });

    function submit(event) {
        event.preventDefault();
        const form = event.target;

        setMyState({
            ...myState,
            firstName: form?.firstName?.value   ? form?.firstName?.value : myState.firstName,
            lastName:  form?.lastName?.value    ? form.lastName?.value   : myState.lastName,
            email:     form?.email?.value       ? form?.email?.value     : myState.email,
            subject:   form?.subject?.value     ? form?.subject?.value   : myState.subject,
            message:   form?.message?.value     ? form?.message?.value   : myState.message
        });
    }

    return (
        <section data-testid="playground">
            <h1>Form</h1>

            <section data-testid="input">
                <form onSubmit={submit}>
                    <label htmlFor="firstName">First Name</label>
                    <input type="text" id="firstName" placeholder="Jane" />

                    <label htmlFor="lastName">Last Name</label>
                    <input type="text" id="lastName" placeholder="Doe" />

                    <label htmlFor="email">Email</label>
                    <input type="text" id="email" placeholder="jane.doe@example.com" />

                    <label htmlFor="subject">Subject</label>
                    <input type="text" id="subject" placeholder="Subject" />

                    <label htmlFor="message">Message</label>
                    <textarea rows="3" id="message" type="text" placeholder="Message"></textarea>

                    <button type="submit" name="update">Update</button>
                </form>
            </section>

            <section data-testid="output">
                <div>firstName: {myState.firstName}</div>
                <div>lastName: {myState.lastName}</div>
                <div>email: {myState.email}</div>
                <div>subject: {myState.subject}</div>
                <div>message: {myState.message}</div>
            </section>
        </section>
    );
};

export default Playground;

这是有问题的测试(第 49 行的断言失败):

import React from 'react';
import {render, screen} from "@testing-library/react";
import userEvent from '@testing-library/user-event';
import Playground from "./Playground";

function getFormFields (screen) {
    const formFields = {};

    formFields.firstName = screen.getByLabelText(/first name/i);
    formFields.lastName = screen.getByLabelText(/last name/i);
    formFields.email = screen.getByLabelText(/email/i);
    formFields.subject = screen.getByLabelText(/subject/i);
    formFields.message = screen.getByLabelText(/^message$/i);

    return formFields;
}

function getOutput (screen) {
    const output = {};

    output.firstName = screen.getByText(/^firstname:/i);
    output.lastName = screen.getByText(/^lastname:/i);
    output.email = screen.getByText(/^email:/i);
    output.subject = screen.getByText(/^subject:/i);
    output.message = screen.getByText(/^message:/i);

    return output;
}

test('Should have an editable First Name field', () => {
    render(<Playground />)
    const fields = getFormFields(screen);

    userEvent.type(fields.firstName, "Matthew");

    expect(fields.firstName).toHaveValue("Matthew");
});

test('User should be able to submit their first name', async () => {
    render(<Playground />)
    const fields = getFormFields(screen),
          submit = screen.getByRole('button', { name: /update/i }),
          output = getOutput(screen);

    userEvent.type(fields.firstName, "Matthew");
    userEvent.click(submit);

    expect(output.firstName).toHaveTextContent("firstName:");
    expect(output.firstName).toHaveTextContent("Matthew");
});

我尝试过的事情

最后的想法

这让我抓狂,因为它 看起来 就像一个非常简单的问题(我并不是在做火箭手术)。如果能得到任何帮助,我将不胜感激!

谢谢!

您需要等待 UI 在某个事件(例如点击)后更改:

import { render, screen, waitFor } from '@testing-library/react';

...

userEvent.click(submit);

await waitFor(() => {
    // The first assertion may not be necessary, as it never changes?
    // And it's not recommended to have multiple assertions in waitFor.
    expect(output.firstName).toHaveTextContent("firstName:");
    expect(output.firstName).toHaveTextContent("Matthew");
});

编辑

还有一个问题,您需要更改您的提交处理程序:

function submit(event) {
    event.preventDefault();
    const form = event.target.elements; // <-- note that it uses 'elements'
    ...

有趣的是,它在“现实生活”中有效,但在测试环境中无效。但是对于 elements 它在这两种情况下都有效。

您可以查看此 MDN article 了解更多信息。