[英]How to mock/spy useState hook in jest?
我試圖監視 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
您需要使用React.useState
而不是單個導入useState
。
我認為是關於代碼如何轉換的,正如您在 babel repl 中看到的useState
來自單個導入的useState
最終與模塊導入之一不同
_react.useState // useState
_react.default.useState // React.useState;
所以你監視_react.default.useState
但你的組件使用_react.useState
。 監視單個導入似乎是不可能的,因為您需要該函數屬於一個對象,這里有一個非常廣泛的指南,解釋了模擬/監視模塊的方法https://github.com/HugoDF/mock-spy-module-進口
正如@Alex Mackay 提到的,您可能想改變測試 React 組件的思維方式,建議使用 react-testing-library,但如果您真的需要堅持使用酶,則無需進行模擬反應庫本身
dieu 的回答讓我找到了正確的方向,我想出了這個解決方案:
jest.mock('react', ()=>({ ...jest.requireActual('react'), useState: jest.fn() })) import { useState } from 'react';
describe("Test", ()=>{ beforeEach(()=>{ useState.mockImplementation(jest.requireActual('react').useState); //other preperations }) //tests })
it("Actual test", ()=>{ useState.mockImplementation(()=>["someMockedValue", someMockOrSpySetter]) })
離別筆記:雖然在“黑匣子”是單元測試中弄臟手在概念上可能有些錯誤,但有時這樣做確實非常有用。
惱人的是 Codesandbox 目前在其測試模塊上遇到問題,所以我無法發布一個工作示例,但我會嘗試解釋為什么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
轉過來,從那以后就沒有接觸過它。
只需要在測試文件中導入 React,例如:
import * as React from 'react';
之后,您可以使用模擬功能。
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);
})
你應該使用 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;
// 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);
});
});
});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.