【实战】一、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 前端自动化测试框架基础入门


7.异步代码的测试方法

安装 axios

js 复制代码
npm i axios@0.19.0 -S

新建 fetchData.js:

js 复制代码
import axios from 'axios'

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

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

js 复制代码
import fetchData from './fetchData'

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

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

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

js 复制代码
import axios from 'axios'

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

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

js 复制代码
import fetchData from './fetchData'

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

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

js 复制代码
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 自带的函数识别结果:

js 复制代码
test('fetchData 返回结果为 Promise: { success: true }', () => {
  return expect(fetchData()).resolves.toMatchObject({
    data: {
      success: true
    }
  })
})
js 复制代码
test('fetchData 返回结果为 404', () => {
  return expect(fetchData()).rejects.toThrow()
})

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

js 复制代码
test('fetchData 返回结果为 Promise: { success: true }', async () => {
  await expect(fetchData()).resolves.toMatchObject({
    data: {
      success: true
    }
  })
})
js 复制代码
test('fetchData 返回结果为 404', async () => {
  await expect(fetchData()).rejects.toThrow()
})

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

js 复制代码
test('fetchData 返回结果为 Promise: { success: true }', async () => {
  const res = await fetchData()
  expect(res.data).toEqual({
      success: true
  })
})
js 复制代码
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

js 复制代码
export default class Counter {
  constructor() {
    this.number = 0
  }
  addOne() {
    this.number += 1
  }
  minusOne() {
    this.number -= 1
  }
}

新建 Counter.test.js

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

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 新增两个方法

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

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)
    })
  })
})

测试日志如下:

bash 复制代码
测试 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 修饰符可以让单元测试只运行这一个测试用例:

js 复制代码
test.only('', () => {})

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


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

相关推荐
lovingsoft2 分钟前
Chrome Lighthouse 详解
前端·chrome
Qlly12 分钟前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝16 分钟前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦17 分钟前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡38 分钟前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI43 分钟前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95271 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端
看浪的路人1 小时前
第9讲:大模型应用监控与可观测性——OpenTelemetry + Prometheus + Grafana 全链路追踪
java·前端·spring
内存过载1 小时前
用表格模拟甘特图:项目排期的土办法
前端·低代码
葡萄城技术团队1 小时前
用 SpreadJS 数据验证实现省市区三级联动
前端