Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTL示例与总结 #76

Open
xiaochengzi6 opened this issue May 20, 2023 · 0 comments
Open

RTL示例与总结 #76

xiaochengzi6 opened this issue May 20, 2023 · 0 comments
Labels

Comments

@xiaochengzi6
Copy link
Owner

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

RTL 测试

一、获取 dom

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

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

参考一下 #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. 鼠标事件: 点击、悬停、移入移出、选中
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

模拟函数

关于模拟函数参考这篇文章:#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()
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant