使用 useState() 钩子测试功能组件时设置状态

IT技术 reactjs enzyme setstate react-hooks
2021-05-11 04:04:58

当我用酶测试类组件时,我可以wrapper.setState({})设置状态。当我使用useState()钩子测试功能组件时,我现在该怎么做

例如在我的组件中,我有:

const [mode, setMode] = useState("my value");

我想mode在我的测试中改变

4个回答

使用钩子中的状态时,您的测试必须忽略状态等实现细节才能正确测试它。您仍然可以确保组件将正确的状态传递给其子组件。

您可以在Kent C. Dodds 撰写的这篇博文中找到一个很好的示例

这是它的摘录和代码示例。

依赖于状态实现细节的测试 -

test('setOpenIndex sets the open index state properly', () => {
  const wrapper = mount(<Accordion items={[]} />)
  expect(wrapper.state('openIndex')).toBe(0)
  wrapper.instance().setOpenIndex(1)
  expect(wrapper.state('openIndex')).toBe(1)
})

不依赖状态实现细节的测试 -

test('counter increments the count', () => {
  const {container} = render(<Counter />)
  const button = container.firstChild
  expect(button.textContent).toBe('0')
  fireEvent.click(button)
  expect(button.textContent).toBe('1')
})

这是我发现的方法,我不是说这是对还是错。就我而言,代码块依赖于设置为特定值的状态。我会保留我对 React 测试的看法。

在您的测试文件中:调整react库的导入

import * as React from 'react'

然后在你的测试中监视 useState 并模拟它的实现

const stateSetter = jest.fn()
jest
.spyOn(React, 'useState')
//Simulate that mode state value was set to 'new mode value'
.mockImplementation(stateValue => [stateValue='new mode value', stateSetter])

请注意,这种模拟 useState 将适用于为您的测试调用 useState 的所有实例,因此如果您正在查看多个状态值,它们都将被设置为“新模式值”。其他人可能会帮你解决这个问题。希望能帮助到你。

在测试文件的顶部,可以首先定义为:

  import { useState } from 'react';

  jest.mock('react', () => ({
    ...jest.requireActual('react'),
    useState: jest.fn()
  }));

  const useStateMock: jest.Mock<typeof useState> = useState as never;

之后,在每个测试中都可以使用不同的值来测试:

  const setValue = jest.fn();
  useStateMock
    .mockImplementation(() => ['value', setValue]);

你应该用这种方式

// setupTests.js
    const { configure } = require('enzyme')
    const Adapter = require('@wojtekmaj/enzyme-adapter-react-17')
    const { createSerializer } = require('enzyme-to-json')

    configure({ adapter: new Adapter() });
    expect.addSnapshotSerializer(createSerializer({
        ignoreDefaultProps: true,
        mode: 'deep',
        noKey: true,
    }));
import React, { useState } from "react";

    const Home = () => {

        const [count, setCount] = useState(0);

        return (
            <section>

                <h3>{count}</h3>
                <span>
                    <button id="count-up" type="button" onClick={() => setCount(count + 1)}>Count Up</button>
                    <button id="count-down" type="button" onClick={() => setCount(count - 1)}>Count Down</button>
                    <button id="zero-count" type="button" onClick={() => setCount(0)}>Zero</button>
                </span>
            </section>
        );

    }

    export default Home;

// index.test.js

    import { mount } from 'enzyme';
    import Home from '../';
    import React, { useState as useStateMock } from 'react';


    jest.mock('react', () => ({
        ...jest.requireActual('react'),
        useState: jest.fn(),
    }));

    describe('<Home />', () => {
        let wrapper;

        const setState = jest.fn();

        beforeEach(() => {
            useStateMock.mockImplementation(init => [init, setState]);
            wrapper = mount(<Home />);
        });

        afterEach(() => {
            jest.clearAllMocks();
        });

        describe('Count Up', () => {
            it('calls setCount with count + 1', () => {
                wrapper.find('#count-up').simulate('click');
                expect(setState).toHaveBeenCalledWith(1);
            });
        });

        describe('Count Down', () => {
            it('calls setCount with count - 1', () => {
                wrapper.find('#count-down').props().onClick();
                expect(setState).toHaveBeenCalledWith(-1);
            });
        });

        describe('Zero', () => {
            it('calls setCount with 0', () => {
                wrapper.find('#zero-count').props().onClick();
                expect(setState).toHaveBeenCalledWith(0);
            });
        });
    });