测试useState时无法读取未定义的属性“ target”

时间:2020-05-26 19:10:51

标签: reactjs enzyme

在将状态从父组件传递到子组件时,我正在尝试这些状态方法

const [bio, setBio] = useState("");
const [gravatar, setGravatar] = useState("");

但我收到此错误

✓应检查EditProfile是否呈现子组件(2毫秒) ✕应该测试生物状态(4毫秒)

●应该渲染›应该测试生物状态

TypeError: Cannot read property 'target' of undefined

调用此测试时,我做错了什么?谢谢。

 it("should test bio state", () => {
        wrapper
            .find("EditProfileForm")
            .props()
            .handleBio();
        expect(setState).toHaveBeenCalledWith("set bio");
    });

editProfile.test.tsx

import React from "react";
import EditProfile from "./editProfile";
import { shallow, mount, render } from "enzyme";
import EditProfileForm from "../forms/editProfile/editForm";
import { createShallow } from "@material-ui/core/test-utils";
import { Provider } from "react-redux";
import { store } from "../../store";

describe("Should render <EditProfile/>", () => {
    let wrapper;
    const setState = jest.fn();
    const useStateSpy = jest.spyOn(React, "useState");
    useStateSpy.mockImplementation((init) => [init, setState]);

    beforeAll(() => {
        wrapper = mount(
            <Provider store={store}>
                <EditProfile />
            </Provider>,
        );
    });

    it("Should render <EditProfile/>", () => {
        expect(wrapper).toHaveLength(1);
    });

    it("Should check if EditProfile renders child component", () => {
        expect(wrapper.find("EditProfileForm")).toHaveLength(1);
    });

    it("should test bio state", () => {
        wrapper
            .find("EditProfileForm")
            .props()
            .handleBio();
        expect(setState).toHaveBeenCalledWith("set bio");
    });
});

editProfile.tsx

import React, { useEffect, Component, useRef, useState } from "react";
import Typography from "@material-ui/core/Typography";
import EditProfileForm from "../forms/editProfile/editForm";
import GridHoc from "../hoc/grid";
import { Grid } from "@material-ui/core";
import storeMethods from "../../common/storeHooks";

function EditProfile(props: any) {
    const [bio, setBio] = useState("");
    const [gravatar, setGravatar] = useState("");
    const mounted = useRef<Object>();
    const { getProfile, profileData, userErr, message, updateProfile } = storeMethods();
    useEffect(() => {
        if (!mounted.current) {
            getProfile();
            mounted.current = true;
        } else {
            setBio(bio ? bio : profileData.bio);
            setGravatar(gravatar ? gravatar : profileData.gravatar);
        }
    });
    const handleSubmit = (e: any) => {
        e.preventDefault();
        const formData = {
            bio,
            gravatar,
        };
        updateProfile(formData);
    };

    return (
        <Grid container={true} justify="center">
            <Grid item={true} xs={12} sm={12} md={8} lg={8}>
                {userErr && <Typography style={{ color: "red" }}>{message || userErr}</Typography>}
                {message && <Typography style={{ color: "green" }}>{message || userErr}</Typography>}
                <EditProfileForm handleBio={(e) => setBio(e.target.value)} handleGravatar={(e) => setGravatar(e.target.value)} onSubmit={handleSubmit} bio={bio} gravatar={gravatar} />
            </Grid>
        </Grid>
    );
}

export default GridHoc(EditProfile);

editProfileForm

import Button from "@material-ui/core/Button";
import FormGroup from "@material-ui/core/FormGroup";
import FormLabel from "@material-ui/core/FormLabel";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import React from "react";
const EditProfileForm = (props: any) => (
    <form onSubmit={props.onSubmit}>
        <Typography variant="h5">Edit Profile</Typography>
        <FormGroup style={{ padding: "30px 0px" }}>
            <FormLabel style={{ display: "block" }}>Bio</FormLabel>
            <TextField
                id="outlined-name"
                style={{
                    width: "100%",
                }}
                name="bio"
                multiline={true}
                rows="3"
                defaultValue={props.bio}
                onChange={props.handleBio}
                margin="normal"
                variant="outlined"
            />
            <FormLabel style={{ display: "block" }}>Gravatar</FormLabel>
            <TextField
                id="outlined-name"
                style={{
                    width: "100%",
                }}
                name="gravatar"
                multiline={true}
                rows="3"
                onChange={props.handleGravatar}
                defaultValue={props.gravatar}
                margin="normal"
                variant="outlined"
            />
        </FormGroup>
        <Button className="subBtn" variant="outlined" color="primary" type="submit">
            Submit
        </Button>
    </form>
);

export default EditProfileForm;

1 个答案:

答案 0 :(得分:1)

测试您调用handleBio的方式有问题。测试应该模拟用户交互,但是这里您以编程方式调用handleBio,这根本没用。 相反,您应该想象用户可以使用您的组件做什么,然后必须执行哪些操作。 在这里,您似乎测试了setBio被调用时handleBio是否被正确调用,这只是测试React正确绑定了道具(并且您不需要/不应该这样做)。 相反,您需要测试handleBio的更改已调用TextField,并且应该在EditProfileForm测试中进行。

为此,您需要在TextField上触发一个更改事件(如果用户更改了TextField值,将会发生这种情况),只有在此之后,才期望handleBio已使用正确的参数调用。

我建议您使用@testing-library/react,这将使您的测试更容易编写。

您可以在两个label上都添加一个TextField属性:

<TextField name="bio" label="bio" />
<TextField name="gravatar" label="gravatar" />

,然后编写以下测试:

// existing imports
import { render as testRender, fireEvent, screen } from '@testing-library/react';

const props = {
  onSubmit: jest.fn(),
  handleBio: jest.fn(),
  handleGravatar: jest.fn(),
}

const render = () => testRender(<EditProfileForm {...props} />;

describe('EditProfile', () => {

  ...

  it('calls handleBio on bio TextField change', () => {
    render();
    const input = screen.getByLabelText('bio');

    fireEvent.change(input, { target: { value: 'new value' } });

    expect(props.handleBio).toHaveBeenCalledTimes(1);
  }
});
相关问题