如何在jest中模拟/监视useState钩子?

xqnpmsa8  于 2023-05-04  发布在  Jest
关注(0)|答案(6)|浏览(159)

我试图监视useState React钩子,但我总是测试失败
这是我的React组件:

const Counter= () => {
    const[counter, setCounter] = useState(0);

    const handleClick=() => {
        setCounter(counter + 1);
    }

    return (
        <div>
            <h2>{counter}</h2>
            <button onClick={handleClick} id="button">increment</button>
        </div>
    )
}

counter.test.js

it('increment counter correctlry', () => {
    let wrapper = shallow(<Counter/>);
    const setState = jest.fn();
    const useStateSpy = jest.spyOn(React, 'useState');

    useStateSpy.mockImplementation((init) => [init, setState]);
     const button = wrapper.find("button")
     button.simulate('click');
     expect(setState).toHaveBeenCalledWith(1);
})

不幸的是,这不起作用,我得到了测试失败的消息:

expected 1
Number of calls: 0
tpxzln5u

tpxzln5u1#

diedu的回答给我指引了正确的方向,我想出了这个解决方案:
1.从react中模拟使用状态,以返回jest.fn()作为useState:
1.1也要在后面立即导入useState--现在它将是ejestmock(从jest.fn()调用返回)

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

1.稍后,在beforeEach中,将其设置为原始useState,以便在所有情况下不需要它被模仿

describe("Test", ()=>{
  beforeEach(()=>{
    useState.mockImplementation(jest.requireActual('react').useState);
    //other preperations
  })
  //tests
})

1.在测试中,根据需要模拟它:

it("Actual test", ()=>{
  useState.mockImplementation(()=>["someMockedValue", someMockOrSpySetter])
})

临别赠言:虽然从概念上讲,在单元测试这个“黑盒”中弄脏双手可能有点错误,但有时候这样做确实非常有用。

axr492tv

axr492tv2#

您需要使用React.useState而不是单个导入useState
我认为是关于代码如何被转译的,正如您在babel repl中看到的,来自单个导入的useState最终与模块导入的useState不同。

_react.useState // useState
_react.default.useState // React.useState;

因此,您监视_react.default.useState,但您的组件使用_react.useState。由于需要函数属于一个对象,因此在单个导入中进行监视似乎是不可能的,这里有一个非常广泛的指南,解释了mocking/spying模块https://github.com/HugoDF/mock-spy-module-import的方法
正如@Alex Mackay所提到的,你可能想改变你对测试react组件的想法,建议你转向react-testing-library,但如果你真的需要坚持使用酶,你不需要走那么远来模拟react library本身。

x8goxv8g

x8goxv8g3#

你只需要在你的测试文件中导入React,就像:

import * as React from 'react';

然后你可以使用mock函数。

import * as React from 'react';

:
:
it('increment counter correctlry', () => {
    let wrapper = shallow(<Counter/>);
    const setState = jest.fn();
    const useStateSpy = jest.spyOn(React, 'useState');

    useStateSpy.mockImplementation((init) => [init, setState]);
     const button = wrapper.find("button")
     button.simulate('click');
     expect(setState).toHaveBeenCalledWith(1);
})
wn9m85ua

wn9m85ua4#

令人烦恼的是,Codesandbox目前在测试模块上遇到了麻烦,所以我不能发布一个工作示例,但我会试图解释为什么mocking useState通常是一件坏事。
用户并不关心是否调用了useState,他们关心的是 * 当我单击increment时,计数应该增加1 *,因此这就是您应该测试的内容。

// App
import React, { useState } from "react";
export default function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
    </div>
  );
}
// Tests
import React from "react";
import App from "./App";
import { screen, render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

describe("App should", () => {
  it('increment count value when "Increment" btn clicked', () => {
    // Render the App
    render(<App />);
    // Get the count in the same way the user would, by looking for 'Count'
    let count = screen.getByText(/count:/);
    // As long as the h1 element contains a '0' this test will pass
    expect(count).toContain(0);
    // Once again get the button in the same the user would, by the 'Increment'
    const button = screen.getByText(/increment/);
    // Simulate the click event
    userEvent.click(button);
    // Refetch the count
    count = screen.getByText(/count:/);
    // The 'Count' should no longer contain a '0'
    expect(count).not.toContain(0);
    // The 'Count' should contain a '1'
    expect(count).toContain(1);
  });
  // And so on...
  it('reset count value when "Reset" btn is clicked', () => {});
  it('decrement count value when "Decrement" btn is clicked', () => {});
});

如果你对这种测试风格感兴趣,一定要看看@testing-library。我大约2年前从enzyme切换过来,从那以后就没有碰过它。

whlutmcx

whlutmcx5#

你应该使用React.useState()而不是useState(),但还有其他方法...在React中,您可以使用此配置设置useState而不使用React

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

简体中文

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

jk9hmnmh6#

你不需要使用`Import * as React from 'react',因为如果你在代码中不使用React.useState,它会导致问题。
我只需要做以下几件事就可以了:

Import React from 'react';

describe('the test suite', () => {
  afterEach(() => restoreAllMocks());
  
  it('the test', () => {
    const setter = jest.fn();
    
    jest.spyOn(React, 'useState').mockImplementation(() => [true, setter]);
    
    // do something
    
    expect(setter).toHaveBeenCalled();
  })
})

相关问题