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

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

## 练习目标

### 功能 3：嵌套评论

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

### 功能 4：回复框

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

## 本次任务的练习要求

* 首先，提取已完成的基础评论框和留言列表组件（如果你还没有拆分组件的话）
* 使用 Testing Library 为上述组件再次编写行为测试
* 新功能必须先写 Cypress E2E 测试，再写组件级别测试

按照以下手法提取组件：

![](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M1VmJSxhLWSbqdIq__i%2F-M1VmJxbD_gXNi1F6f8o%2Fextract-components.png?generation=1583245514360750\&alt=media)

## 组件化与 UI 测试

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

### UI 组件树

![](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M1VmJSxhLWSbqdIq__i%2F-M1VmJxdXZScMlz_66L8%2Fcomponent-tree.png?generation=1583245514379544\&alt=media)

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

![](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M1jB6hekAeEzuJfbO3s%2F-M1jB7M1r9nvthlBNUcz%2Fui.png?generation=1583487159311364\&alt=media)

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

## 组件行为测试

![](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M1VmJSxhLWSbqdIq__i%2F-M1VmJxf9hx0lUVO29qs%2Freact-testing-library-enzyme-comparison.png?generation=1583245514752126\&alt=media)

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

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

![](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M1VmJSxhLWSbqdIq__i%2F-M1VmJxpcPGgNP1pSzpg%2Fread-run-fit.gif?generation=1583245515396527\&alt=media)

> [Write tests, not too many, mostly integration | Kent C. Dodds Blog](https://kentcdodds.com/post/write-integration-tests/)

### 测试组件的交互行为

```javascript
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)
})
```

### 测试组件单元的接口集成

```javascript
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 设计？
* 先写测试与后补测试有何不同？什么样的组件接口测起来很困难？
* 为什么在组件级别需要你更关注于组件行为而不是样式？
