场景
在使用Jest编写React项目单元测试时,会遇到比如点击某个按钮(查看详情),然后页面跳转到某个指定路由页面(详情页)的测试场景。基于BDD的思想,结合Jest与React-Testing-Library,本文总结这种场景下单元测试编写思路以及遇到的问题。
// Component.tsx
import React from 'react'
import { useHistory } from 'react-router-dom'
const Component = () => {
const history = useHistory()
const handleClick = (evt: React.MouseEvent) => {
history.push(`/pageDetails?id=${1}`)
}
return (
<div>
<button onClick={handleClick}>查看详情</button>
</div>
)
}
export default Component
思路
通过定位到某个按钮元素,模拟点击按钮后,断言
- 点击事件对应的函数被调用
- 当前路由是我们指定跳转后的路由
pathname - 路由如携带参数
search为我们模拟的参数。
// component.test.js
import React from 'react'
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event'
import {createMemoryHistory} from 'history'
import Component from './component'
describe('测试组件页', () => {
it('点击查看详情按钮,跳转到详情页', () => {
render(<Component />)
const history = createMemoryHistory()
// 定位元素模拟点击按钮
act(() => {
userEvent.click(screen.getByText('详细信息'))
})
// 断言
expect(history.location.pathname).toBe("/pageDetails") // 断言指定路由
expect(history.location.search).toBe("?id=1") // 断言携带参数
})
遇到的问题
- 直接运行写好的单元测试时,点击按钮调用的是我们组件真实代码绑定的函数
handleClick。会出现测试不通过Expected: "/pageDetails";Received: "/"。 - 问题原因是
createMemoryHistory在执行时获取的是默认的路径/
- 方案一可以是把点击跳转调用的函数放在单元测试中进行,即创建一个模拟函数,再运行测试。参考下文代码实现。
- 方案二也可以是使用
Router包裹Component组件,在Router的props传入history后,模拟点击,再断言。参考createMemoryHistory
方案一代码实现
// component.test.js
import React from 'react'
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event'
import {createMemoryHistory} from 'history'
import Component from './component'
describe('测试组件页', () => {
it('点击查看详情按钮,跳转到详情页', () => {
render(<Component />)
const history = createMemoryHistory()
const clickHandler = jest.fn(evt => { // 创建模拟点击事件函数
evt.preventDefault()
evt.stopPropagation()
history.push('/pageDetails?id=1')
})
screen.getByText('查看详情').onclick = evt => clickHandler(evt) // 将模拟点击事件函数绑定到按钮
act(() => {
userEvent.click(screen.getByText('详细信息'))
})
expect(clickHandler).toHaveBeenCalled() // 断言点击事件被调用
expect(history.location.pathname).toBe("/pageDetails") // 断言指定路由
expect(history.location.search).toBe("?id=6") // 断言携带参数
})
})