xiaochengzi6 / Blog

个人博客
GNU Lesser General Public License v2.1
0 stars 0 forks source link

RTL示例与总结 #76

Open xiaochengzi6 opened 1 year ago

xiaochengzi6 commented 1 year ago

这里介绍的是 jest 和 rtl 库的使用

RTL 测试

一、获取 dom

查找 Dom 元素使用 getxxxqueryxxxfindxxxx 这三种查找方式

通过在其后缀 All 之后就会查找多个元素,如果没有使用在后缀All的这种形式,查找元素存在多个的条件下就会抛出错误

参考一下 https://github.com/xiaochengzi6/Blog/issues/67

这里不经常用 所以简单的过了一下了解 这三种查找方式的区别就行

// App 组件
function App() {
  return (
    <form>
      <label htmlFor="title-input">Title</label>
      <input id="title-input" />

      <label htmlFor="content-input">Content</label>
      <textarea id="content-input" />

      <label htmlFor="tags-input">Tags</label>
      <input id="tags-input" />

      <button type="submit">Submit</button>
    </form>
  )
}
// App.test.js 测试组件

import {render, screen} from "@testing-ibrary/react"

test('renders a form with title, content, tags, and a submit button', () => {
  render(<Editor />)
  screen.getByLabelText(/title/i)
  screen.getByLabelText(/content/i)
  screen.getByLabelText(/tags/i)
  screen.getByText(/submit/i)
})

二、触发事件

使用 userEvent 来触发事件,比如下面的点击

// Editor.js 
function Editor() {
  function handleSubmit(e) {
    e.preventDefault()
    setIsSaving(true)
  }
  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="title-input">Title</label>
      <input id="title-input" />

      <button type="submit" disabled={isSaving}>
        Submit
      </button>
    </form>
  )
}

// Editor.test.js
test('renders a form with title, content, tags, and a submit button', () => {
  render(<Editor />)

  const submitButton = screen.getByText(/submit/i)

  userEvent.click(submitButton)

  expect(submitButton).toBeDisabled()
})

三、测试 hook 组件

有两个比较关键的 api renderact render 是渲染组件,调用 act 是进行对组件的更新

关于 act:https://legacy.reactjs.org/docs/test-utils.html#act

// useCounter.js
import * as React from 'react'

function useCounter({initialCount = 0, step = 1} = {}) {
  const [count, setCount] = React.useState(initialCount)
  const increment = () => setCount((c) => c + step)
  const decrement = () => setCount((c) => c - step)
  return {count, increment, decrement}
}

export {useCounter}

// useCounter.test.js
import * as React from 'react'
import {render, act} from '@testing-library/react'
import {useCounter} from './use-counter'

test('exposes the count and increment/decrement functions', () => {
  let result
  // 由于是测试组件所以使用一个函数进行包裹
  function TestComponent() {
    result = useCounter()
    return null
  }
  // 这种属于是测试组件的方法,这样测hook也没问题但没有使用 renderHook 那样优雅
  render(<TestComponent />)
  expect(result.count).toBe(0)

  // 使用 act 进行组件的更新
  act(() => result.increment())
  expect(result.count).toBe(1)
  act(() => result.decrement())
  expect(result.count).toBe(0)
})

事件

在 RTL 中有两种事件测试的包 userEventfireEvent

userEvent可以完整的模仿一套事件的触发,而 fireEvent是直接触发了事件,使用 dispatchEvnet方法。这就会导致在一前者的文件提交较大,后者更轻量化,在一些简单的事件操作下使用 fireEvent就行。

diapatchEvent 参考:https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent


var event = new Event('build');

// Listen for the event. elem.addEventListener('build', function (e) { ... }, false);

// Dispatch the event. elem.dispatchEvent(event);


使用到 `userEvent` 事件去触发
常见的有以下几种
1. 鼠标事件: 点击、悬停、移入移出、选中
~~~js
import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'

test('click', () => {
  render(
    <div>
      <label htmlFor="checkbox">Check</label>
      <input id="checkbox" type="checkbox" />
    </div>,
  )

  // 点击触发
  userEvent.click(screen.getByText('Check'))
  expect(screen.getByLabelText('Check')).toBeChecked()
})
  1. 键盘事件:键入、下落、抬升、

使用 type 方法在 inputtextarea去写入

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

test('type', () => {
  render(<textarea />)

 // 
  userEvent.type(screen.getByRole('textbox'), 'Hello,{enter}World!')
  expect(screen.getByRole('textbox')).toHaveValue('Hello,\nWorld!')
})

这块东西有些多,没总结过来,具体的事件看官网吧...

参考:https://testing-library.com/docs/ecosystem-user-event

Jest 测试框架

全局钩子

  1. affterAll:大意就是在当前文件下运行完所有的测试结果后会掉用你通过 affterAll 传入的 函数 ,如果返回值是 promise 则会等待它处理完。

  2. affterEach 在每个测试文件完成之后调用一次传入的函数,如果返回的是 promiseorgenerator 会等待它处理完

  3. beforeAll 在运行此文件中的任何测试之前运行函数,如果返回的是 promiseorgenerator 会等待它处理完

  4. beforeEach 在运行此文件中的每个测试之前运行一个函数, 在运行此文件中的每个测试之前运行一个函数

前面四种函数可以接收两个参数 (fn, timeout), 回调函数 fn 和 超时时间 默认为 5s 超过 5s 就会结束

参考:https://www.jestjs.cn/docs/api#afterallfn-timeout

模拟函数

关于模拟函数参考这篇文章:https://github.com/xiaochengzi6/Blog/issues/66

清除

在一些不支持使用 afterEach 这样自动注入到测试环境下的话需要自己去手动清除,但 jest、mocha 都会自动的去清除

import {cleanup, render} from '@testing-library/react'
import test from 'ava'

// 比如在 ava 测试框架
// 需要手动清除 
test.afterEach(cleanup)

使用定时器需要注意的事项

在使用定时器时,在一些其它测试框架一般会采取虚拟定时器来模仿,为了确保在测试后不会改变原定时器功能尽量在测试后去恢复

在测试中所有的定时器都使用 虚拟定时器

beforeEach(() => {
  jest.useFakeTimers()
})

在测试结束后运行所有挂起的定时器并将虚拟定时器还原成定时器

afterEach(() => {
  // 运行所有挂起的定时器
  jest.runOnlyPendingTimers()
  // 使用
  jest.useRealTimers()
})