create-react-app 默认添加了 Jest
我们还需要另一个测试库帮助渲染组件react-testing-library
安装

  1. npm install --save-dev @testing-library/react @testing-library/jest-dom

在components目录中新建Note.test.js文件(与Note组件在相同目录),编写测试代码

  1. import React from 'react'
  2. import '@testing-library/jest-dom/extend-expect'
  3. import { render } from '@testing-library/react'
  4. import Note from './Note'
  5. test('renders content', () => {
  6. const note = {
  7. content: 'Component testing is done with react-testing-library',
  8. important: true,
  9. }
  10. const component = render(<Note note={note} />)
  11. expect(component.container).toHaveTextContent(
  12. 'Component testing is done with react-testing-library'
  13. )
  14. })

如果执行命令**npm test**, 测试会在watch模式下执行,如果控制台报错需要安装watchman
如果不要在watch模式下执行,则使用命令**CI=true npm test**
之所以将测试文件与组件放在同一目录,是因为create-react-app创建项目时默认这样配置

Searching for content in a component

查找组件中的内容有多种不同的方法

  1. import React from 'react'
  2. import '@testing-library/jest-dom/extend-expect'
  3. import { render } from '@testing-library/react'
  4. import Note from './Note'
  5. test('renders content', () => {
  6. const note = {
  7. content: 'Component testing is done with react-testing-library',
  8. important: true,
  9. }
  10. const component = render(<Note note={note} />)
  11. // method 1
  12. expect(component.container).toHaveTextContent(
  13. 'Component testing is done with react-testing-library'
  14. )
  15. // method 2
  16. const element = component.getByText('Component testing is done with react-testing-library')
  17. expect(element).toBeDefined()
  18. // method 3
  19. const div = component.container.querySelector('.note')
  20. expect(div).toHaveTextContent('Component testing is done with react-testing-library')
  21. })

Debugging test

render返回的对象有一个debug方法,可以将组件的HTML打印到控制台

  1. // ...
  2. test('renders content', () => {
  3. const note = {
  4. content: 'Component testing is done with react-testing-library',
  5. important: true,
  6. }
  7. const component = render(<Note note={note} />)
  8. component.debug()
  9. // ...
  10. }

image.png
还可以使用prettyDOM方法查找一小部分HTML, 在控制台打印出来

  1. // ...
  2. import { prettyDOM } from '@testing-library/dom'
  3. test('renders content', () => {
  4. const note = {
  5. content: 'Component testing is done with react-testing-library',
  6. important: true,
  7. }
  8. const component = render(<Note note={note} />)
  9. const li = component.container.querySelector('li')
  10. console.log(prettyDOM(li))
  11. // ...
  12. }

Clicking buttons in tests

测试按钮被点击

  1. // ...
  2. import { render, fireEvent } from '@testing-library/react'
  3. test('clicking the button calls event handler once', () => {
  4. const note = {
  5. content: 'Component testing is done with react-testing-library',
  6. important: true,
  7. }
  8. // 用jest定义mock函数
  9. const mockHandler = jest.fn()
  10. const component = render(<Note note={note} toggleImportance={mockHandler} />)
  11. const button = component.getByText('make not important')
  12. fireEvent.click(button)
  13. // 测试mock function是否只被调用了一次
  14. expect(mockHandler.mock.calls).toHaveLength(1)
  15. })

Tests for the Togglable component

测试Togglable组件

  1. import React from 'react'
  2. import '@testing-library/jest-dom/extend-expect'
  3. import { render, fireEvent } from '@testing-library/react'
  4. import Togglable from './Togglable'
  5. describe('<Togglable />', () => {
  6. let component
  7. beforeEach(() => {
  8. component = render(
  9. <Togglable buttonLabel='show...'>
  10. <div className='testDiv' />
  11. </Togglable>
  12. )
  13. })
  14. test('renders its children', () => {
  15. expect(component.container.querySelector('.testDiv')).toBeDefined()
  16. })
  17. test('at start the children are not displayed', () => {
  18. const div = component.container.querySelector('.togglableContent')
  19. expect(div).toHaveStyle('display: none')
  20. })
  21. test('after clicking the button, children are displayed', () => {
  22. const button = component.getByText('show...')
  23. fireEvent.click(button)
  24. const div = component.container.querySelector('.togglableContent')
  25. expect(div).not.toHaveStyle('display:none')
  26. })
  27. test('toggled content can be closed', () => {
  28. const button = component.getByText('show...')
  29. fireEvent.click(button)
  30. const closeButton = component.getByText('cancel')
  31. fireEvent.click(closeButton)
  32. const div = component.container.querySelector('.togglableContent')
  33. expect(div).toHaveStyle('display: none')
  34. })
  35. })

beforeEach会在每个test被执行前调用

Testing the forms

fireEvent除了可以点击按钮,还可以模拟文本输入

  1. import React from 'react'
  2. import { render, fireEvent } from '@testing-library/react'
  3. import '@testing-library/jest-dom/extend-expect'
  4. import NoteForm from './NoteForm'
  5. test('<NoteForm /> updates parent state and calls onSubmit', () => {
  6. const createNote = jest.fn()
  7. const component = render(<NoteForm createNote={createNote} />)
  8. const input = component.container.querySelector('input')
  9. const form = component.container.querySelector('form')
  10. fireEvent.change(input, {
  11. target: { value: 'testing of forms could be easier' },
  12. })
  13. fireEvent.submit(form)
  14. expect(createNote.mock.calls).toHaveLength(1)
  15. expect(createNote.mock.calls[0][0].content).toBe('testing of forms could be easier')
  16. })

Test coverage

使用如下命令查看测试覆盖范围,会生成一共原始的HTML报表

  1. CI=true npm test -- --coverage

到目前为止做的都是单元测试unit tests,单元测试测试了单个组件的正确功能
如果要测试多个组件协作,需要从服务器模拟数据,进行集成测试integration tests,下一节将采用端到端测试 end to end tests整个应用。