使用 useState() 钩子测试功能组件时设置状态
Posted
技术标签:
【中文标题】使用 useState() 钩子测试功能组件时设置状态【英文标题】:Set state when testing functional component with useState() hook 【发布时间】:2019-08-15 22:52:11 【问题描述】:当我用酶测试类组件时,我可以使用wrapper.setState()
来设置状态。当我使用useState()
钩子测试功能组件时,我现在该怎么做?
例如在我的组件中:
const [mode, setMode] = useState("my value");
我想在我的测试中更改mode
【问题讨论】:
【参考方案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测试一样好,它不是单元测试,消费者是开发者而不是浏览器以上是关于使用 useState() 钩子测试功能组件时设置状态的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 useState - 钩子将类组件(输入,条件)更改为功能组件?