React 测试库 - FAQ

本文详细介绍了如何在React应用中使用ReactTestingLibrary正确测试onChange事件,区分不同输入类型的处理方式,并探讨了浅渲染与深度模拟组件、快照差异、异步操作测试等问题,以及组件测试层次的选择原则。
摘要由CSDN通过智能技术生成
1、如何测试 input onChange 事件?
import React from 'react'
import {render, fireEvent} from '@testing-library/react'

test('change values via the fireEvent.change method', () => {
  const handleChange = jest.fn()
  const {container} = render(<input type="text" onChange={handleChange} />)
  const input = container.firstChild
  fireEvent.change(input, {target: {value: 'a'}})
  expect(handleChange).toHaveBeenCalledTimes(1)
  expect(input.value).toBe('a')
})

test('select drop-downs must use the fireEvent.change', () => {
  const handleChange = jest.fn()
  const {container} = render(
    <select onChange={handleChange}>
      <option value="1">1</option>
      <option value="2">2</option>
    </select>,
  )
  const select = container.firstChild
  const option1 = container.getElementsByTagName('option').item(0)
  const option2 = container.getElementsByTagName('option').item(1)

  fireEvent.change(select, {target: {value: '2'}})

  expect(handleChange).toHaveBeenCalledTimes(1)
  expect(option1.selected).toBe(false)
  expect(option2.selected).toBe(true)
})

test('checkboxes (and radios) must use fireEvent.click', () => {
  const handleChange = jest.fn()
  const {container} = render(<input type="checkbox" onChange={handleChange} />)
  const checkbox = container.firstChild
  fireEvent.click(checkbox)
  expect(handleChange).toHaveBeenCalledTimes(1)
  expect(checkbox.checked).toBe(true)
})

如果你曾经使用过 Enzyme 或者 React 的 TestUtils,你可能习惯于像这样修改输入:

input.value = 'a'
Simulate.change(input)

我们不能用 React 测试库(React Testing Library)来做这件事,因为 React 实际上会跟踪你在输入上赋值给 value 属性的任何时间,所以当你触发 change 事件时,React 认为 value 实际上并没有改变。

这对 Simulate 有效,因为它们使用内部 API 来触发特殊的模拟事件。而 React 测试库,我们尽量避免实现细节,以使你的测试更具弹性。

因此,我们为 change 事件处理程序设置了以 React 无法跟踪的方式设置属性的方式。这就是为什么你必须将 value 作为 change 方法调用的一部分传递的原因。

2、我可以用这个库编写单元测试吗?

当然可以!您可以使用这个库编写单元测试和集成测试。如果您想对高级组件进行单元测试,请参见下文以了解如何模拟依赖项(因为这个库故意不支持浅渲染)。此项目中的测试显示了几个使用该库进行单元测试的示例。

编写测试时,请记住:测试越接近软件的使用方式,它就越能给您信心。

3、如果我不能使用浅渲染,我如何在测试中模拟组件?

总的来说,你应该避免 mock 组件(参见指导原则部分)。但是,如果你需要的话,那么尝试使用 Jest 的 mock 功能。我发现特别有用的一种情况是用于动画库。我不想让我的测试等待动画结束。

jest.mock('react-transition-group', () => {
  const FakeTransition = jest.fn(({children}) => children)
  const FakeCSSTransition = jest.fn(props =>
    props.in ? <FakeTransition>{props.children}</FakeTransition> : null,
  )
  return {CSSTransition: FakeCSSTransition, Transition: FakeTransition}
})

test('you can mock things with jest.mock', () => {
  const {getByTestId, queryByTestId} = render(
    <HiddenMessage initialShow={true} />,
  )
  expect(queryByTestId('hidden-message')).toBeTruthy() // we just care it exists
  // hide the message
  fireEvent.click(getByTestId('toggle-message'))
  // in the real world, the CSSTransition component would take some time
  // before finishing the animation which would actually hide the message.
  // So we've mocked it out for our tests to make it happen instantly
  expect(queryByTestId('hidden-message')).toBeNull() // we just care it doesn't exist
})

请注意,因为它们是 Jest 模拟函数(jest.fn()),如果你愿意,你也可以对它们进行断言。

打开完整的测试以查看完整示例。

这看起来比浅渲染(shallow rendering)需要更多的工作(实际上也确实如此),但只要你模拟的东西足够接近你要模拟的东西,它就会给你更多的信心。

如果你想把事情做得更像浅渲染,那么你可以尝试这样做。

要了解 Jest 模拟函数的工作原理,请阅读我的博客文章:“但究竟,什么是 JavaScript 模拟函数?”。

4、关于enzyme,为什么会出现“结构复杂、特征繁多”和“鼓励不良的测试实践”的情况?

大多数破坏性特性都与鼓励测试实施细节有关。 首先,这些是浅渲染,允许通过组件构造函数选择渲染元素的API,以及允许您获取并与组件实例(及其状态/属性)进行交互的API(大多数酶的包装API都允许这样做)。

这个库的指导原则是:

你的测试越接近你的软件使用方式,它们能给你的信心就越多。 - 2018年2月17日

由于用户不能直接与您的应用程序的组件实例进行交互,断言其内部状态或它们渲染的组件,或调用其内部方法,因此在测试中进行这些操作会降低它们能够给予的信心。

这并不是说从未有用例去做这些事情,因此应该能够完成,只是不是测试和反应组件的默认和自然方式。

5、为什么快照差异比较不起作用?

如果你使用 snapshot-diff 库来保存快照差异,它将无法直接工作,因为这个库使用了可变的DOM。变化不会返回新的对象,所以 snapshot-diff 会认为它是同一个对象并避免对它进行差异分析。

幸运的是,有一个简单的方法可以让它工作:在将DOM传递给 snapshot-diff 时克隆它。它看起来像这样:

const firstVersion = container.cloneNode(true)
// Do some changes
snapshotDiff(firstVersion, container.cloneNode(true))
6、该如何修复“更新未被 act(...) 包装”的警告?

这个警告通常是由异步操作引起的,该操作在测试结束后触发更新。有两种方法可以解决此问题:

  1. 使用 waitFor 或 find* 查询等异步实用工具,在测试中等待操作结果。例如:const userAddress = await findByLabel(/address/i)。
  2. 对异步操作进行模拟,使其不触发状态更新。

一般来说,方法1更受推崇,因为它更符合用户与应用交互的预期。

此外,当你在考虑如何编写能够给你信心的测试并避免这些警告时,你可能会发现这篇博客文章很有帮助。

7、我应该测试组件树的哪个级别?子级、父级还是两者都要?

遵循这个库的指导原则,理解测试是如何围绕用户体验和与应用功能的交互来组织的,而不是围绕具体的组件本身,这是很有用的。在某些情况下,例如对于可重复使用的组件库,将开发人员包含在测试用户列表中,并分别测试每个可重复使用的组件可能是有用的。在其他情况下,组件树的特定分解只是实现细节,测试该树中的每个组件可能会导致问题(见https://kentcdodds.com/blog/avoid-the-test-user)。

在实践中,这通常意味着测试组件树的高级部分以模拟真实的用户交互。至于是否值得在此基础上进行更高或更低级别的测试,这取决于权衡和成本效益(有关不同级别的测试的更多信息,请参见https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests)。

有关此主题的更深入的讨论,请观看此视频

  • 40
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值