首页 > 解决方案 > 使用 Mount 时 Enzyme 返回的节点多于现有节点

问题描述

组件.tsx

import React, { ChangeEvent, FormEvent, useEffect, useState } from "react";
import { Form, FormControl, FormGroup, FormLabel } from "react-bootstrap";
import Button from "react-bootstrap/Button";
import Card from "react-bootstrap/Card";
import { useHistory } from "react-router-dom";
import { StorageKeys } from "../ProtectedRoute";
import "./styles.scss";

const Login = () => {
    const history = useHistory();
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");

    const handleSetEmail = (event: ChangeEvent<HTMLInputElement>) =>
        setEmail(event.target.value);

    const handleSetPassword = (event: ChangeEvent<HTMLInputElement>) =>
        setPassword(event.target.value);

    const handleSubmit = (event: FormEvent<HTMLElement>) => {
        event.preventDefault();
        console.log("email::", email);
        console.log("password::", password);
        localStorage.setItem(StorageKeys.TOKEN, "TODO: Auth");
        history.push("/");
    };

    useEffect(() => {
        localStorage.removeItem(StorageKeys.TOKEN);
    }, []);

    return (
        <div id="login">

            <Card id="loginCard">
                <Card.Header>Login</Card.Header>
                <Card.Body>

                    <Form onSubmit={handleSubmit}>
                        <FormGroup>
                            <FormLabel>Email address</FormLabel>
                            <FormControl type="email" id="email" placeholder="Enter email"
                                         value={email} onChange={handleSetEmail}
                                         required={true} />
                        </FormGroup>

                        <FormGroup>
                            <FormLabel>Password</FormLabel>
                            <FormControl type="password" id="password" placeholder="Password"
                                         value={password} onChange={handleSetPassword}
                                         required={true} />
                        </FormGroup>

                        <div className={"button-container"}>
                            <Button id="submit" variant="primary" type="submit">
                                Submit
                            </Button>
                        </div>
                    </Form>
                </Card.Body>
            </Card>
        </div>
    );
};

export default Login;

这在shallow用于渲染组件时有效:

登录.test.tsx

import { mount, shallow } from "enzyme";
import React from "react";
import Login from "./index";

describe("Login Component", () => {
    test("can properly submit form", () => {
        jest.spyOn(window.localStorage.__proto__, "removeItem");

        const wrapper = shallow(<Login />);

        // This works just find, finds only the one #email input.
        const emailInput = wrapper.find("#email");
        emailInput.simulate("change", { target: { value: testLoginData.email } });
    });
});

使用时mount会引发错误:

Error: Method “simulate” is meant to be run on 1 node. 2 found instead
import { mount, shallow } from "enzyme";
import React from "react";
import Login from "./index";

describe("Login Component", () => {
    test("can properly submit form", () => {
        jest.spyOn(window.localStorage.__proto__, "removeItem");

        const wrapper = mount(<Login />);
        const emailInput = wrapper.find("#email");

        // This will now complain about there being too many nodes.
        emailInput.simulate("change", { target: { value: testLoginData.email } });
    });
});

是什么赋予了?我需要使用 mount 来进行我正在进行的测试,为什么肯定只有一个元素时会找到多个元素。

我可以使用以下方法修补它,但我不应该……对吗?!

emailInput.at(0).simulate("change", { target: { value: testLoginData.email } });

标签: reactjsjestjsenzyme

解决方案


所以这是因为你<FormControl是第一个id并且<input是第二个(反之亦然)。

有很多方法:

  1. .at(0)会起作用,但是这样你永远不会知道你是否(因为代码中的错误)渲染了多个元素。如果条件渲染{someFlag && <....中假定互斥的条件不是,则可能会发生这种情况。所以真的,这是一个糟糕的方式。
  2. 模拟FormControl为最终元素 - 所以<input将不再返回.find()(老实说从未使用过,只是假设它会工作 - 但仍然看起来很混乱,并且每个测试文件都需要额外的样板代码,所以不是非常少数的方法):
jest.mock('../FormControl.jsx', () => null);
  1. 用于hostNodes()仅过滤本机元素(如<span>要返回):
const emailInput = wrapper.find("#email").hostNodes();

我投票支持第 3 个选项,因为它对于捕获代码逻辑的错误是最可靠且仍然安全的。


推荐阅读