跳到主要内容

示例

快速入门

这是帮助你入门的最小示例设置。如果你想知道每一行的作用,可以滚动到 注释版。如果你想了解高级的测试配置 滚动到 完整示例

import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import '@testing-library/jest-dom'
import Fetch from './fetch'

test('loads and displays greeting', async () => {
// 准备
render(<Fetch url="/greeting" />)

// 操作
await userEvent.click(screen.getByText('Load Greeting'))
await screen.findByRole('heading')

// 断言
expect(screen.getByRole('heading')).toHaveTextContent('hello there')
expect(screen.getByRole('button')).toBeDisabled()
})
快速入门 (注释版)
// import react-testing methods
import {render, screen} from '@testing-library/react'
// userEvent library simulates user interactions by dispatching the events that would happen if the interaction took place in a browser.
import userEvent from '@testing-library/user-event'
// add custom jest matchers from jest-dom
import '@testing-library/jest-dom'
// the component to test
import Fetch from './fetch'

test('loads and displays greeting', async () => {
// Render a React element into the DOM
render(<Fetch url="/greeting" />)

await userEvent.click(screen.getByText('Load Greeting'))
// wait before throwing an error if it cannot find an element
await screen.findByRole('heading')

// assert that the alert message is correct using
// toHaveTextContent, a custom matcher from jest-dom.
expect(screen.getByRole('heading')).toHaveTextContent('hello there')
expect(screen.getByRole('button')).toBeDisabled()
})

完整示例

__tests__/fetch.test.jsx
import React from 'react'
import {rest} from 'msw'
import {setupServer} from 'msw/node'
import {render, fireEvent, waitFor, screen} from '@testing-library/react'
import '@testing-library/jest-dom'
import Fetch from '../fetch'

const server = setupServer(
rest.get('/greeting', (req, res, ctx) => {
return res(ctx.json({greeting: 'hello there'}))
}),
)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

test('loads and displays greeting', async () => {
render(<Fetch url="/greeting" />)

fireEvent.click(screen.getByText('Load Greeting'))

await waitFor(() => screen.getByRole('heading'))

expect(screen.getByRole('heading')).toHaveTextContent('hello there')
expect(screen.getByRole('button')).toBeDisabled()
})

test('handles server error', async () => {
server.use(
rest.get('/greeting', (req, res, ctx) => {
return res(ctx.status(500))
}),
)

render(<Fetch url="/greeting" />)

fireEvent.click(screen.getByText('Load Greeting'))

await waitFor(() => screen.getByRole('alert'))

expect(screen.getByRole('alert')).toHaveTextContent('Oops, failed to fetch!')
expect(screen.getByRole('button')).not.toBeDisabled()
})

我们推荐使用 Mock Service Worker 以声明方式模 拟 API 通信,而不是 stub window.fetch 方法,或是依赖第三方的适配器。


步骤分解

导入依赖(Imports)

// import dependencies
import React from 'react'

// import API mocking utilities from Mock Service Worker
import {rest} from 'msw'
import {setupServer} from 'msw/node'

// import react-testing methods
import {render, fireEvent, waitFor, screen} from '@testing-library/react'

// add custom jest matchers from jest-dom
import '@testing-library/jest-dom'
// the component to test
import Fetch from '../fetch'
test('loads and displays greeting', async () => {
// 准备(Arrange)
// 调用(Act)
// 断言(Assert)
})

模拟(Mock)

使用 msw 提供的 setupServer 函数来模拟被测试组件发起 API 请求。

// declare which API requests to mock
const server = setupServer(
// capture "GET /greeting" requests
rest.get('/greeting', (req, res, ctx) => {
// respond using a mocked JSON body
return res(ctx.json({greeting: 'hello there'}))
}),
)

// establish API mocking before all tests
beforeAll(() => server.listen())
// reset any request handlers that are declared as a part of our tests
// (i.e. for testing one-time error scenarios)
afterEach(() => server.resetHandlers())
// clean up once the tests are done
afterAll(() => server.close())

// ...

test('handles server error', async () => {
server.use(
// override the initial "GET /greeting" request handler
// to return a 500 Server Error
rest.get('/greeting', (req, res, ctx) => {
return res(ctx.status(500))
}),
)

// ...
})

准备(Arrange)

render 方法渲染一个 React 组件到 DOM。

render(<Fetch url="/greeting" />)

调用(Act)

fireEvent 方法可以让你通过派发事件来模拟 用户动作。

fireEvent.click(screen.getByText('Load Greeting'))

// wait until the `get` request promise resolves and
// the component calls setState and re-renders.
// `waitFor` waits until the callback doesn't throw an error

await waitFor(() =>
// getByRole throws an error if it cannot find an element
screen.getByRole('heading'),
)

断言(Assert)

// assert that the alert message is correct using
// toHaveTextContent, a custom matcher from jest-dom.
expect(screen.getByRole('alert')).toHaveTextContent('Oops, failed to fetch!')

// assert that the button is not disabled using
// toBeDisabled, a custom matcher from jest-dom.
expect(screen.getByRole('button')).not.toBeDisabled()

被测试组件(System Under Test)

fetch.jsx
import React, {useState, useReducer} from 'react'
import axios from 'axios'

const initialState = {
error: null,
greeting: null,
}

function greetingReducer(state, action) {
switch (action.type) {
case 'SUCCESS': {
return {
error: null,
greeting: action.greeting,
}
}
case 'ERROR': {
return {
error: action.error,
greeting: null,
}
}
default: {
return state
}
}
}

export default function Fetch({url}) {
const [{error, greeting}, dispatch] = useReducer(
greetingReducer,
initialState,
)
const [buttonClicked, setButtonClicked] = useState(false)

const fetchGreeting = async url =>
axios
.get(url)
.then(response => {
const {data} = response
const {greeting} = data
dispatch({type: 'SUCCESS', greeting})
setButtonClicked(true)
})
.catch(error => {
dispatch({type: 'ERROR', error})
})

const buttonText = buttonClicked ? 'Ok' : 'Load Greeting'

return (
<div>
<button onClick={() => fetchGreeting(url)} disabled={buttonClicked}>
{buttonText}
</button>
{greeting && <h1>{greeting}</h1>}
{error && <p role="alert">Oops, failed to fetch!</p>}
</div>
)
}