前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何测试驱动开发 React 组件?

如何测试驱动开发 React 组件?

作者头像
狂奔滴小马
发布2022-03-29 19:29:18
2.2K0
发布2022-03-29 19:29:18
举报
文章被收录于专栏:前端专享

什么是 TDD

TDD(Test-driven development),就是测试驱动开发,是敏捷开发中的一项核心实践和技术,也是一种软件设计方法论。

它的原理就是在编写代码之前先编写测试用例,由测试来决定我们的代码。而且 TDD 更多地需要编写独立的测试用例,比如只测试一个组件的某个功能点,某个工具函数等。

TDD 的过程

  • 编写测试用例
  • 运行测试,测试失败
  • 修改代码
  • 测试通过
  • 重构/优化代码
  • 新增功能,重复上述步骤

在某种程度上,它可能在初学者看来是单调乏味或者不切实际的,但是严格按照这个步骤来做这件事,让你自己决定测试用例是否对你的组件有帮助,会让测试用例变得有意义。

本文将以创建一个 Confirmation 组件来说明,如何在 React 中如何实现测试驱动开发。

Confirmation 组件的特点:

  • Confirmation 标题
  • 确认描述 —— 接收外部程序想要确认的问题
  • 一个确认的按钮,支持外部回调函数
  • 一个取消的按钮,支持外部回调函数

这两个按钮都不知道点击时接下来要做什么事,因为它超出了组件的职责范围,但是组件应该接收这些点击按钮的回调事件。先找个设计图:

那么,让我们开始吧。

测试组件

首先使用 create-react-app 初始化一个 react 项目。目前 cra 已经内置了 @testing-library/react 作为测试框架。

代码语言:javascript
复制
npx create-react-app my-react-app

我们先从测试文件开始。先创建了组件的目录“Confirmation” 并在其中添加一个“index.test.js”文件。

确保渲染测试

第一个测试相当抽象。仅仅需要检查组件是否展现(任何东西) ,以确保这个组件是存在。但是实际上,我将要测试的组件还不存在。

首先通过 getByRole 方法 查找 role属性等于dialog能否文档中找到。

role 属性可能不太常用, 当现有的 HTML 标签不能充分表达语义性的时候,就可以借助 role 来说明. 例如点击的按钮,就是 role="button" ;会让这个元素可点击;也可以使用 role 属性告诉辅助设备(如屏幕阅读器)这个元素所扮演的角色。

代码语言:javascript
复制
import React from 'react'
import { render } from '@testing-library/react'

describe('Confirmation component', () => {
  it('should render', () => {
    const { getByRole } = render(<Confirmation />)
    expect(getByRole('dialog')).toBeInTheDocument()
  })
})

运行测试并且监听

代码语言:javascript
复制
yarn test --watch

用 “脚趾头” 思考都知道这肯定是不能通过测试的。接下来,让我们创建一个足够满足这个测试的组件:

代码语言:javascript
复制
import React from 'react'

const Confirmation = () => {
  return <div role="dialog"></div>
}

export default Confirmation

然后把这个组件导入到测试中,它现在通过了。

接下来,组件应该有一个动态标题。

动态标题测试

创建一个测试用例:

代码语言:javascript
复制
it('should have a dynamic title', () => {
  const title = '标题'
  const { getByText } = render(<Confirmation title={title} />)
  expect(getByText(title)).toBeInTheDocument()
})

测试失败了,修改代码使它通过:

代码语言:javascript
复制
import React from 'react'

const Confirmation = ({ title }) => {
  return (
    <div role="dialog">
      <h1>{title}</h1>
    </div>
  )
}

export default Confirmation

下一个特性,这个组件中存在一个确认问题提示

动态问题测试

这个问题也是动态的,这样它就可以从组件外部传入。

代码语言:javascript
复制
it('should have a dynamic confirmation question', () => {
  const question = 'Do you confirm?'
  const { getByText } = render(<Confirmation question={question} />)
  expect(getByText(question)).toBeInTheDocument()
})

测试再次失败,修改代码让它通过:

代码语言:javascript
复制
import React from 'react'

const Confirmation = ({ title, question }) => {
  return (
    <div role="dialog">
      <h1>{title}</h1>
      <div>{question}</div>
    </div>
  )
}

export default Confirmation

确认按钮测试

接下来是确认按钮测试。我们首先要检查组件上是否有一个按钮,上面写着“确认”。

编写测试用例代码:

代码语言:javascript
复制
it('should have an "OK" button', () => {
  const { getByRole } = render(<Confirmation />)
  expect(getByRole('button', { name: '确认' })).toBeInTheDocument()
})

在这里使用 name 选项,因为我们知道这个组件中至少还有一个按钮,需要更具体地说明查找断言的是哪个按钮

组件代码:

代码语言:javascript
复制
import React from 'react'

const Confirmation = ({ title, question }) => {
  return (
    <div role="dialog">
      <h1>{title}</h1>
      <div>{question}</div>
      <button>确认</button>
    </div>
  )
}

export default Confirmation

取消按钮测试

同样对“取消”按钮做同样的事情:

测试:

代码语言:javascript
复制
it('should have an "取消" button', () => {
  const { getByRole } = render(<Confirmation />)
  expect(getByRole('button', { name: '取消' })).toBeInTheDocument()
})

组件代码:

代码语言:javascript
复制
import React from 'react'

const Confirmation = ({ title, question }) => {
  return (
    <div role="dialog">
      <h1>{title}</h1>
      <div>{question}</div>
      <button>确认</button>
      <button>取消</button>
    </div>
  )
}

export default Confirmation

好了。现在我们得到了我们想要的组件渲染的 HTML ,现在我想要确保我可以从外部传递这个组件的按钮的回调函数,并确保它们在单击按钮时被调用。

那么我将从“确认”按钮的测试开始:

代码语言:javascript
复制
it('should be able to receive a handler for the "确认" button and execute it upon click', () => {
  const onOk = jest.fn()
  const { getByRole } = render(<Confirmation onOk={onOk} />)
  const okButton = getByRole('button', { name: '确认' })

  fireEvent.click(okButton)

  expect(onOk).toHaveBeenCalled()
})

先用 jest.fn 创建一个模拟函数,将其作为“onOk”处理函数传递给组件,模拟单击“确认”按钮,并断言函数已被调用。

个测试显然失败了,下面是补充代码:

代码语言:javascript
复制
import React from 'react'

const Confirmation = ({ title, question, onOk }) => {
  return (
    <div role="dialog">
      <h1>{title}</h1>
      <div>{question}</div>
      <button onClick={onOk}>确认</button>
      <button>取消</button>
    </div>
  )
}

export default Confirmation

最后,让我们对“取消”按钮做同样的事情:

测试:

代码语言:javascript
复制
it('should be able to receive a handler for the "取消" button and execute it upon click', () => {
  const onCancel = jest.fn()
  const { getByRole } = render(<Confirmation onCancel={onCancel} />)
  const okButton = getByRole('button', { name: '取消' })

  fireEvent.click(okButton)

  expect(onCancel).toHaveBeenCalled()
})

组件:

代码语言:javascript
复制
import React from 'react'

const Confirmation = ({ title, question, onOk, onCancel }) => {
  return (
    <div role="dialog">
      <h1>{title}</h1>
      <div>{question}</div>
      <button onClick={onOk}>确认</button>
      <button onClick={onCancel}>取消</button>
    </div>
  )
}

export default Confirmation

下面是完整的测试文件:

代码语言:javascript
复制
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Confirmation from './index'

describe('Confirmation component', () => {
  it('should render', () => {
    const { getByRole } = render(<Confirmation />)
    expect(getByRole('dialog')).toBeInTheDocument()
  })

  it('should have a dynamic title', () => {
    const title = '标题'
    const { getByText } = render(<Confirmation title={title} />)
    expect(getByText(title)).toBeInTheDocument()
  })

  it('should have a dynamic confirmation question', () => {
    const question = 'Do you confirm?'
    const { getByText } = render(<Confirmation question={question} />)
    expect(getByText(question)).toBeInTheDocument()
  })

  it('should have an "确认" button', () => {
    const { getByRole } = render(<Confirmation />)
    expect(getByRole('button', { name: '确认' })).toBeInTheDocument()
  })

  it('should have an "取消" button', () => {
    const { getByRole } = render(<Confirmation />)
    expect(getByRole('button', { name: '取消' })).toBeInTheDocument()
  })

  it('should be able to receive a handler for the "确认" button and execute it upon click', () => {
    const onOk = jest.fn()
    const { getByRole } = render(<Confirmation onOk={onOk} />)
    const okButton = getByRole('button', { name: '确认' })

    fireEvent.click(okButton)

    expect(onOk).toHaveBeenCalled()
  })

  it('should be able to receive a handler for the "取消" button and execute it upon click', () => {
    const onCancel = jest.fn()
    const { getByRole } = render(<Confirmation onCancel={onCancel} />)
    const okButton = getByRole('button', { name: '取消' })

    fireEvent.click(okButton)

    expect(onCancel).toHaveBeenCalled()
  })
})

虽然这个组件没有样式,或者说我们还可以优化,添加跟多的功能,以上步骤已经充分展示了测试驱动开发的逻辑。

TDD 一步一步地引导完成组件特性的规范,确保我们在组件重构或者他人修改代码的时候能够遵循现有开发的逻辑。这这是 TDD 的优势。

调试

我们可以使用 debug 打印渲染的 html 结构

代码

代码语言:javascript
复制
it('should be able to receive a handler for the "取消" button and execute it upon click', () => {
  const onCancel = jest.fn()
  const { getByRole, debug } = render(<Confirmation onCancel={onCancel} />)

  debug()
})

这样可以方便我们查找 dom。

小结

当然 @testing-library/react 还有很多方便的 api。大家可以自行查阅。

未来可能会出一些文章关于测试的文章。例如:

如何出测试 react hooks ?

如何测试 react 路由?

如何测试接口?

希望这篇文章对大家有所帮助,也可以参考我往期的文章或者在评论区交流你的想法和心得,欢迎一起探索前端。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年11月30日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 TDD
  • TDD 的过程
  • Confirmation 组件的特点:
  • 测试组件
    • 确保渲染测试
      • 动态标题测试
        • 动态问题测试
          • 确认按钮测试
            • 取消按钮测试
            • 调试
            • 小结
            相关产品与服务
            项目管理
            CODING 项目管理(CODING Project Management,CODING-PM)工具包含迭代管理、需求管理、任务管理、缺陷管理、文件/wiki 等功能,适用于研发团队进行项目管理或敏捷开发实践。结合敏捷研发理念,帮助您对产品进行迭代规划,让每个迭代中的需求、任务、缺陷无障碍沟通流转, 让项目开发过程风险可控,达到可持续性快速迭代。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档