【问题标题】:Set state when testing functional component with useState() hook使用 useState() 钩子测试功能组件时设置状态
【发布时间】:2019-08-15 22:52:11
【问题描述】:

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

例如在我的组件中:

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

我想在我的测试中更改mode

【问题讨论】:

    标签: reactjs enzyme setstate react-hooks


    【解决方案1】:

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

      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]);
    
    

    【讨论】:

      【解决方案2】:

      你应该这样使用

      // 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);
                  });
              });
          });
      

      【讨论】:

        【解决方案3】:

        这是我发现的方法,我并不是说这是对还是错。在我的例子中,一段代码依赖于被设置为特定值的状态。我会保留我对 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 的所有实例,因此如果您正在查看多个状态值,它们将全部设置为“新模式值” .其他人也许可以帮助您解决这个问题。希望对您有所帮助。

        【讨论】:

        • 2 个 useState 的情况如何?
        • 在有 2 个或更多 useStates 的情况下,我们使用了 mockImplementationOnce 并取得了一些成功,但我会警告你它并不漂亮。
        【解决方案4】:

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

        您可以在由 Kent C. Dodds 撰写的 blog post 中找到一个很好的示例。

        这里是摘录的代码示例。

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

        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')
        })
        

        【讨论】:

        • 哦.. 所以当我们使用钩子时我们无法测试状态
        • 目前不直接。我很难想象这是怎么可能的,因为钩子的语法依赖于调用顺序而不是命名。我把它看成是无法访问私有的测试类——拥有访问权限很好,但这通常意味着你可以写得更好:)
        • 嗯,我想测试确实不应该依赖状态变化,而是应该测试外观
        • 所以我可以说著名的 Kent C. Dodds 真的错了。当大量依赖只知道如何发推文的程序员的话时,就会发生这种情况。我为 IBM 工作,我们有义务测试这些钩子。显然,我们必须测试挂钩的功能,我们不能忽视它们存在且至关重要的事实。我遵循了这篇帖子blog.carbonfive.com/2019/08/05/… 的一些指导,而且我很快就会在这里发布和回答,让你知道如何真正测试钩子,这不像肯特说的那样。他错了。
        • 第二个测试和E2E测试一样好,它不是单元测试,消费者是开发者而不是浏览器
        猜你喜欢
        • 1970-01-01
        • 2020-11-30
        • 2019-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-24
        • 2021-04-13
        • 2020-05-19
        相关资源
        最近更新 更多