【实战】一、Jest 前端自动化测试框架基础入门(三) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(三)

【实战】一、Jest 前端自动化测试框架基础入门(三) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(三)


学习内容来源:Jest入门到TDD/BDD双实战_前端要学的测试课


相对原教程,我在学习开始时(2023.08)采用的是当前最新版本:

版本
@babel/core ^7.16.0
@pmmmwh/react-refresh-webpack-plugin ^0.5.3
@svgr/webpack ^5.5.0
@testing-library/jest-dom ^5.17.0
@testing-library/react ^13.4.0
@testing-library/user-event ^13.5.0
babel-jest ^27.4.2
babel-loader ^8.2.3
babel-plugin-named-asset-import ^0.3.8
babel-preset-react-app ^10.0.1
bfj ^7.0.2
browserslist ^4.18.1
camelcase ^6.2.1
case-sensitive-paths-webpack-plugin ^2.4.0
css-loader ^6.5.1
css-minimizer-webpack-plugin ^3.2.0
dotenv ^10.0.0
dotenv-expand ^5.1.0
eslint ^8.3.0
eslint-config-react-app ^7.0.1
eslint-webpack-plugin ^3.1.1
file-loader ^6.2.0
fs-extra ^10.0.0
html-webpack-plugin ^5.5.0
identity-obj-proxy ^3.0.0
jest ^27.4.3
jest-enzyme ^7.1.2
jest-resolve ^27.4.2
jest-watch-typeahead ^1.0.0
mini-css-extract-plugin ^2.4.5
postcss ^8.4.4
postcss-flexbugs-fixes ^5.0.2
postcss-loader ^6.2.1
postcss-normalize ^10.0.1
postcss-preset-env ^7.0.1
prompts ^2.4.2
react ^18.2.0
react-app-polyfill ^3.0.0
react-dev-utils ^12.0.1
react-dom ^18.2.0
react-refresh ^0.11.0
resolve ^1.20.0
resolve-url-loader ^4.0.0
sass-loader ^12.3.0
semver ^7.3.5
source-map-loader ^3.0.0
style-loader ^3.3.1
tailwindcss ^3.0.2
terser-webpack-plugin ^5.2.5
web-vitals ^2.1.4
webpack ^5.64.4
webpack-dev-server ^4.6.0
webpack-manifest-plugin ^4.0.2
workbox-webpack-plugin ^6.4.1"

具体配置、操作和内容会有差异,“坑”也会有所不同。。。


一、Jest 前端自动化测试框架基础入门

  • 一、Jest 前端自动化测试框架基础入门(一)

  • 一、Jest 前端自动化测试框架基础入门(二)

7.异步代码的测试方法

安装 axios

npm i axios@0.19.0 -S

新建 fetchData.js:

import axios from 'axios'

export const fetchData = (fn) => {
  axios.get('http://www.dell-lee.***/react/api/demo.json').then(res => fn(res.data))
}

新建单元测试文件 fetchData.test.js:

import fetchData from './fetchData'

// 回调类型异步函数的测试
test('fetchData 返回结果为 { su***ess: true }', (done) => {
  fetchData((data) => {
    expect(data).toEqual({
      su***ess: true
    })
    // 只有当 done 函数被执行到才认为是测试用例执行结束
    done();
  })
})

不使用 done 的话,测试用例执行到 fetchData 之后直接就返回 pass

还有一种情况,将 Promise 对象直接返回出来:修改 fetchData.js:

import axios from 'axios'

export const fetchData = () => {
  return axios.get('http://www.dell-lee.***/react/api/demo.json')
}

相应修改单元测试文件 fetchData.test.js:

import fetchData from './fetchData'

test('fetchData 返回结果为 Promise: { su***ess: true }', () => {
  return fetchData().then((res) => {
    expect(res.data).toEqual({
      su***ess: true
    })
  })
})

若是想要单独测试 404,可以修改为如下

import fetchData from './fetchData'

test('fetchData 返回结果为 404', () => {
 expect.assertions(1) // 下面的 expect 至少执行一个
  return fetchData().catch((e) => {
    expect(e.toString().indexOf('404') > -1).toBe(true)
  })
})

若是不使用 expect.assertions ,当测试 接口访问成功,没走 catch 时,相当于啥也没有执行,也会通过,加上后若是接口访问成功会报错:Expected one assertion to be called but received zero assertion calls.

还有可以使用 expect 自带的函数识别结果:

test('fetchData 返回结果为 Promise: { su***ess: true }', () => {
  return expect(fetchData()).resolves.toMatchObject({
    data: {
      su***ess: true
    }
  })
})
test('fetchData 返回结果为 404', () => {
  return expect(fetchData()).rejects.toThrow()
})

除了使用 return 还可以使用 async…await 的语法:

test('fetchData 返回结果为 Promise: { su***ess: true }', async () => {
  await expect(fetchData()).resolves.toMatchObject({
    data: {
      su***ess: true
    }
  })
})
test('fetchData 返回结果为 404', async () => {
  await expect(fetchData()).rejects.toThrow()
})

还可以使用 async…await 先拿到响应结果,再判断:

test('fetchData 返回结果为 Promise: { su***ess: true }', async () => {
  const res = await fetchData()
  expect(res.data).toEqual({
      su***ess: true
  })
})
test('fetchData 返回结果为 404', async () => {
  expect.assertions(1) // 下面的 expect 至少执行一个
  try {
    await fetchData()
  } catch (e) {
    expect(e.toString()).toEqual('Error: Request failed with status code 404.')
  }
})

8.Jest 中的钩子函数

Jest 中的钩子函数指的是在 Jest 执行过程中到某一特定时刻被自动调用的函数,类似 Vue/React 中的生命周期函数

新建 Counter.js

export default class Counter {
  constructor() {
    this.number = 0
  }
  addOne() {
    this.number += 1
  }
  minusOne() {
    this.number -= 1
  }
}

新建 Counter.test.js

import Counter from "./Counter";

describe('测试 Counter', () => {

  const counter = new Counter();
  
  test('测试 addOne 方法', () => {
    counter.addOne()
    expect(counter.number).toBe(1)
  })
  
  test('测试 minusOne 方法', () => {
    counter.minusOne()
    expect(counter.number).toBe(0)
  })
})

运行测试用例,直接通过,但是两个测试用例共用了一个实例 counter,相互之间有影响,这显然是不可以的,可以引入 Jest 的 钩子函数来做预处理

修改 Counter.test.js

import Counter from "./Counter";

describe('测试 Counter', () => {

  let counter = null

  beforeAll(() => {
    console.log('beforeAll')
  })

  beforeEach(() => {
    console.log('beforeEach')
    counter = new Counter();
  })

  afterEach(() => {
    console.log('afterEach')
    // counter = null
  })

  afterAll(() => {
    console.log('afterAll')
  })
  
  test('测试 addOne 方法', () => {
    console.log('测试 addOne ')
    counter.addOne()
    expect(counter.number).toBe(1)
  })
  
  test('测试 minusOne 方法', () => {
    console.log('测试 minusOne ')
    counter.minusOne()
    expect(counter.number).toBe(-1)
  })
})

这样就不会相互之间产生影响了

编辑 Counter.js 新增两个方法

export default class Counter {
  constructor() {
    this.number = 0
  }
  addOne() {
    this.number += 1
  }
  addTwo() {
    this.number += 2
  }
  minusOne() {
    this.number -= 1
  }
  minusTwo() {
    this.number -= 2
  }
}

这时候测试文件怎么写呢?很显然功能有分类,可以使用 describe

编辑 Counter.test.js

import Counter from "./Counter";

describe('测试 Counter', () => {

  let counter = null

  beforeAll(() => {
    console.log('beforeAll')
  })

  beforeEach(() => {
    console.log('beforeEach')
    counter = new Counter();
  })

  afterEach(() => {
    console.log('afterEach')
    // counter = null
  })

  afterAll(() => {
    console.log('afterAll')
  })
  
  describe('测试“增加”相关的方法', () => {
    test('测试 addOne 方法', () => {
      console.log('测试 addOne ')
      counter.addOne()
      expect(counter.number).toBe(1)
    })
    test('测试 addTwo 方法', () => {
      console.log('测试 addTwo ')
      counter.addTwo()
      expect(counter.number).toBe(2)
    })
  })
  
  
  describe('测试“减少”相关的方法', () => {
    test('测试 minusOne 方法', () => {
      console.log('测试 minusOne ')
      counter.minusOne()
      expect(counter.number).toBe(-1)
    })
    test('测试 minusTwo 方法', () => {
      console.log('测试 minusTwo ')
      counter.minusTwo()
      expect(counter.number).toBe(-2)
    })
  })
})

测试日志如下:

测试 Counter
    测试“增加”相关的方法
      √ 测试 addOne 方法 (6ms)
      √ 测试 addTwo 方法 (4ms)
    测试“减少”相关的方法
      √ 测试 minusOne 方法 (4ms)
      √ 测试 minusTwo 方法 (4ms)

  console.log Counter.test.js:8
    beforeAll

  console.log Counter.test.js:12
    beforeEach

  console.log Counter.test.js:27
    测试 addOne

  console.log Counter.test.js:17
    afterEach

  console.log Counter.test.js:12
    beforeEach

  console.log Counter.test.js:32
    测试 addTwo

  console.log Counter.test.js:17
    afterEach

  console.log Counter.test.js:12
    beforeEach

  console.log Counter.test.js:41
    测试 minusOne

  console.log Counter.test.js:17
    afterEach

  console.log Counter.test.js:12
    beforeEach

  console.log Counter.test.js:46
    测试 minusTwo

  console.log Counter.test.js:17
    afterEach

  console.log Counter.test.js:22
    afterAll

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        4.411s

9.钩子函数的作用域

每一个 describe 都可以有自己的 beforeAll、afterAll、beforeEach、afterEach,执行顺序是从外往内。

外部的钩子函数可以对当前 describe 所有的测试用例起作用,而内部的只对内部的测试用例起作用,这就是钩子函数的作用域。

可以自行编写尝试,这里就不再赘述了。

还有一个单元测试小技巧,test 使用 only 修饰符可以让单元测试只运行这一个测试用例:

test.only('', () => {})

注意,代码执行顺序中,最先执行的是不包含在任何测试用例和钩子函数中的语句(直接暴露在各个 describe 内部最外层的语句),且只执行一次,后续才是测试用例和钩子函数的执行。


本文仅作记录, 实战要点待后续专文总结,敬请期待。。。

转载请说明出处内容投诉
CSS教程_站长资源网 » 【实战】一、Jest 前端自动化测试框架基础入门(三) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(三)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买