任务 2:测试驱动组件单元接口

Hey,你好,我是 JimmyLv 吕靖,这是你开始 前端 TDD 练习的第 12 天,今天我们会以 CommentBox 项目作为练习,学习如何由外到内驱动出组件树的拆分,以及如何通过 Testing Library 测试驱动开发组件的接口,面向接口编程。

练习目标

功能 3:嵌套评论

评论可以嵌套,并且可以再次回复,保留 2 个层级,或可以支持无限层级的嵌套。

功能 4:回复框

回复评论框组件需要相同样式,想一想怎么复用呢?当然也需要支持自定义回复框。

本次任务的练习要求

  • 首先,提取已完成的基础评论框和留言列表组件(如果你还没有拆分组件的话)

  • 使用 Testing Library 为上述组件再次编写行为测试

  • 新功能必须先写 Cypress E2E 测试,再写组件级别测试

按照以下手法提取组件:

组件化与 UI 测试

在组件化出现之前,我们不谈 UI 的单元测试,哪怕是对于 UI 页面的测试来说都是一件非常困难的事情。其实组件化并不全是为了复用,很多情况下也恰恰是为了分治,从而我们可以分组件对 UI 页面进行开发,然后分别对其进行单元测试。

UI 组件树

React 已经让 UI 测试变得容易很多,React 组件都可以被简化为这样一个表达式,即 UI = f(data),这个纯函数返回的只是一个描述 UI 组件应该是什么样子的虚拟 DOM,本质上就是一个树形的数据结构。

给这个纯函数输入一些应用程序的状态,就会得到相应的 UI 描述的输出,这个过程不会去直接操作实际的 UI 元素,也不会产生所谓的副作用。

组件行为测试

Enzyme 让我们在针对某个上层组件进行测试时,可以不用渲染它的子组件,所以就不用再担心子组件的表现和行为,这样就可以只对特定组件的逻辑及其渲染输出进行测试了。

但是这并不能给你以信心,我们在测试的时候应该尽可能模拟真实用户的行为。否则就会出现以下的情况,每个部位的功能都能正常工作,但是却没有集成到一起。

Write tests, not too many, mostly integration | Kent C. Dodds Blog

测试组件的交互行为

import { render } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

test('should only show button when click input', () => {
  const { queryByPlaceholderText, queryByTestId } = render(<AddComment />)

  userEvent.click(queryByPlaceholderText('请输入新留言…'))
  expect(queryByTestId('reply-button')).toHaveTextContent('回 复')

  userEvent.click(queryByTestId('cancel-button'))
  expect(queryByTestId('reply-button')).toBe(null)
})

测试组件单元的接口集成

import { render } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

test('should call sortBy when select sort selections', () => {
  let sortHandler = jest.fn()
  const { queryByTestId, queryByText } = render(<CommentTitle comments={comments} onSort={sortHandler} />)

  userEvent.click(queryByTestId('sort-comment'))
  userEvent.click(queryByText('从旧到新'))

  expect(sortHandler).toBeCalledWith('oldest')
})

也请你思考一下

  • Cypress 和 Testing Library 的测试侧重点是什么?

  • 面向接口编程,如何由测试驱动出更好的组件 props 设计?

  • 先写测试与后补测试有何不同?什么样的组件接口测起来很困难?

  • 为什么在组件级别需要你更关注于组件行为而不是样式?

最后更新于