Upload组件单元测试

上一篇文章中我们用React, Typescript开发了一个Upload组件, 为了保证组件的健壮, 可用在这篇文章中我们将对该组件进行单元测试

在开始写测试用例之前我们先安装测试必须的依赖包@testing-library/jest-dom/extend-expect该报主要用于模拟dom, @testing-library/react该依赖包是React提供的供我们测试React组件

import '@testing-library/jest-dom/extend-expect'
import React from 'react'
import {render, renderResult, fireEvent, wait, createEvent} from '@testing-library/react'
import axios from 'axios'

import Upload, {UploadProps} from './upload'

const testProps: UploadProps = {
  action: 'http://www.baidu.com',
  onSuccess: jest.fn(),
  onChange: jest.fn(),
  onRemove: jest.fn(),
  drag: true
}

首先我们导入了测试需要的依赖包, 然后创建了一个testProps, 该props里包括Upload组件的所需属性

let wrapper: renderResult, fileInput: HTMLInputElement, uploadArea: HTMLElement

describe('test upload component', () => {
  beforeEach(() => {
    wrapper = render(<Upload {...testProps}>Upload Files</Upload>)
    fileInput = wrapper.container.querySelector('.r-file-input')
    uploadArea = wrapper.queryByText('Upload Files')
  })
})

在开始测试前我们需要render出一个虚拟的Uplaod组件, 接下来的测试中我们需要在虚拟的Upload组件中做一些操作

const testFile = new File(['abc'], 'test.txt', {type: 'txt'}) 

describe('test upload component', () => {
  beforeEach(() => {
    ...
  })
  it('upload process', async () => {
    const {queryByText} = wrapper
    // 期望uploadArea是HTML
    expect(uploadArea).toBeInTheDocument()
    // 期望fileInput是不可见的
    expect(fileInput).not.toBeVisible()
    // 对input模拟触发change事件并上传testFile
    fireEvent.change(fileInput, {target: {files: [testFile]}})
    // 期望出现spinner(上传时旋转的菊花动画)
    expect(queryByText('spinner')).toBeInTheDocument()
    // 一段时间后期望出现test.txt
    await wait(() => {
      expect(queryByText('test.txt')).toBeInTheDocument()
    })
    // 期望出现上传完成后的icon
    expect(queryByText('check-circle')).toBeInTheDocument()
    // 期望触发上传成功事件
    expect(testProps.onSuccess).toHaveBeenCalledWith('ok', testFile)
    expect(testProps.onChange).toHaveBeenCalledWith('testFile)

    // 期望找到`times`
    expect(queryByText('times')).toBeInTheDocument()
    // 触发click
    fireEvent.click(queryByText('times'))
    expect(queryByText('test.txt')).not.toBeInTheDocument()
    // 期望触发onRemove后返回文件信息
    expect(testProps.onRemove).toHaveBeenCalledWith(expect.objectContaining({
      raw: testFile,
      status: 'success',
      name: 'test.txt'
    }))
  })
}

第一个测试的是上传以及上传进度, 接下来完成拖拽测试

it('drag and drop file', async () => {
  fireEvent.dragOver(uplodArea)
  expect(uploadArea).toHaveClass('.is-dragover')
  fireEvent.dragLeave(uploadArea)
  expect(uploadArea).not.toHaveClass('.is-dragover')

  const mockDropEvent = createEvent.drop(uploadArea)
  Object.defineProperty(mockDropEvent, 'dataTransfer', {
    value: {
      files: [testFile]
    }
  })
  fireEvent(uploadArea, mockDropEvent)
  await wait(() => {
    expect(wrapper.queryByText('test.txt')).toBeInTheDocument()
  })
  expect(testProps.onSuccess).toHaveBeenCalledWith('ok', testFile)
})

由于jest-dom不支持drop事件, 所以我们需要用createEvent模拟一个drop事件, 可以看到我们创建了一个mockDropEvent事件并为其传入相应的参数, 最后模拟触发该事件以获取期望的结果.

最后说一下我们在测试之处有个actionprops, 这涉及到了如何测试异步请求, 在此我们实现一下

jest.mock(axios)
const mockAxios = axios as jest.Mocked<typeof axios>

describe('upload component', () => {
  ...
  it('test upload process', () => {
    mockAxios.post.mockResolveValue({'data': 'ok'})
    ...
  })
})