使用Jest进行Node.js单元测试的实践指南

一. 为之前的文件模块todo list项目做测试

  • 先安装测试依赖jest yarn add --dev jest
  • 根据文档创建文件sum.jssum.test.js复制代码
js 复制代码
// sum.js
function sum(a, b) {
  return a + b;
}

module.exports = sum;

// sum.test.js
const sum = require('./sum')

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
})
  • 在package.json中添加脚本,之后运行yarn test
js 复制代码
  "scripts": {
    "test": "jest"
  },
  • 如果能得到以下图片的运行结果,则说明测试用例被正确执行了
  • 如果错了为怎么样见下图

二.正式开始为项目做测试

  • 主要测试db.js中的read和write
  • 添加__tests__文件夹,这是jest的约定
  • 在此文件夹下创建db.spec.js,这样命名是单元测试不成文的规定
  • 先写一个最基本的测试用例,期待db.read是一个函数
js 复制代码
const db = require('../db.js')

describe('db', fn => {
  it('can read', ()=>{
    expect(db.read instanceof Function).toBe(true)
  })
  it('can write', ()=>{})
})

三. 单元测试的写法

  • 一般单元测试是白盒测试,知道代码怎么写的,按照写的代码来测试
  • 如何测试读文件,初步想法先在某个路径创建一个文件,然后去读这个路径上的文件,如果能读出就表示正确
  • 但是这样子做会有问题,依赖了外部环境,比如此路径已经有了这个文件,就会产生bug
  • 单元测试不能与外界打交道,与外界打交道是集成测试应该做的
  • 我们通过jest.mock来做,新建__mocks__/fs.js,实现一个最简单的例子
js 复制代码
// __mocks__/fs.js
const fs = jest.genMockFromModule('fs')
fs.x = () => {
  console.log('hi')
  return 'xxx'
}
module.exports = fs;

// db.spec.js
const db = require('../db.js')
const fs = require('fs')
jest.mock('fs')

describe('db', fn => {
  it('can read', ()=>{
    expect(fs.x()).toBe('xxx')
  })
})
  • 测试读文件db.read,其实就是fs.read,所以需要在fs.read上做一些手脚
  • 最终测试db.read的代码
js 复制代码
// fs.js
// 这段代码是使用Jest测试框架中的jest.genMockFromModule函数来创建一个名为fs的模拟模块。在测试中,有时需要模拟一些Node.js核心模块,如文件系统(fs)模块,以便在测试过程中可以控制和检查模块的行为。
const fs = jest.genMockFromModule('fs')
// 这一行使用 jest.requireActual 获取实际的 fs 模块,保存在 _fs 变量中。这将允许模拟模块在需要时调用真实的 fs 模块功能。
const _fs = jest.requireActual('fs')

Object.assign(fs, _fs)

const mocks = {}

//  这个函数用于设置模拟的文件读取操作的期望结果。您可以通过调用 fs.setMock 来指定特定路径的文件读取应该返回的错误和数据。s
fs.setMock = (path, error, data) => {
  mocks[path] = [error, data]
}

// 这个函数被用来模拟 fs.readFile 函数。它接受文件路径、选项和回调函数,检查是否有为特定路径设置的模拟结果。如果有模拟结果,
// 它会调用回调函数,并传递预先设置的错误和数据。如果没有模拟结果,它将调用真实的 _fs.readFile 函数。
fs.readFile = (path, options, callback) => {
  if(callback === undefined) {
    callback = options
  }
  if(path in mocks) {
    callback(...mocks[path])
  } else {
    _fs.readFile(path, options, callback)
  }
}

module.exports = fs;
  • db.spec.js
js 复制代码
const db = require('../db.js')
const fs = require('fs')
// 这段代码是使用Jest测试框架中的jest.mock函数来模拟Node.js的文件系统(fs)模块。jest.mock函数用于模拟模块,以便在测试过程中控制和检查模块的行为,而不实际执行真实的模块代码。
jest.mock('fs')

describe('db', fn => {
  it('can read', async ()=>{
    const data = [{title: 'hi', done: true}]
    fs.setMock('/xxx', null, JSON.stringify(data))
    const list = await db.read('/xxx')
    expect(list).toStrictEqual(data)
  })
})

四. 测试写文件行为

  • 这里要注意,并非真正的去写一个文件,而是把文件写到任意路径
  • fs.js
js 复制代码
const writeMocks = {}

fs.setWriteFileMock = (path, fn) => {
  writeMocks[path] = fn
}

fs.writeFile = (path, data, options, callback) => {
  if (callback === undefined) {
    callback = options
  }

  if(path in writeMocks) {
    writeMocks[path](path, data, options, callback)
  } else {
    _fs.writeFile(path, data, options, callback)
  }
}
  • db.spec.js
js 复制代码
  it('can write', async () => {
    let fakeFile
    fs.setWriteFileMock('/yyy', (path, data, callback) => {
      fakeFile = data
      callback(null)
    })
    const list = [{title: '吃饭',done:true}, {title: '洗脚', done: true}]
    await db.write(list, '/yyy')
    expect(fakeFile).toBe(JSON.stringify(list))
  })

五. 增加clearMocks

  • 我们需要在每一个测试结束后清理测试数据,避免单元测试互相干扰
js 复制代码
// fs.js
fs.clearMocks = () => {
  readMocks = {}
  writeMocks = {}
}

// db.spec.js

afterEach(()=>{
  fs.clearMocks()
})

六. 最后总结

  • 如何测试

    • 选择测试工具:jest
    • 设计测试用例
    • 写测试,运行测试,改代码
  • 单元测试、功能测试、集成测试的区别

    • 单元测试不应该与外界打交道(那是集成测试要做的)
    • 单元测试的对象是函数
    • 功能测试的对象是模块
    • 集成测试的对象是系统
    • 一名优秀的开发者应该优先保证能做到单元测试
相关推荐
sdgsdgdsgc1 小时前
Next.js企业级应用开发:SSR、ISR与性能监控方案
开发语言·前端·javascript
哲此一生9841 小时前
搭建Vue3工程(去除不必要的文件)
前端·javascript·vue.js
摇滚侠3 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
黑云压城After4 小时前
H5使用环信实现视频或语音通话
前端·javascript·vue.js
未来之窗软件服务5 小时前
自己写算法(九)网页数字动画函数——东方仙盟化神期
前端·javascript·算法·仙盟创梦ide·东方仙盟·东方仙盟算法
程序员小凯5 小时前
Spring Boot测试框架详解
java·spring boot·后端
你的人类朋友6 小时前
什么是断言?
前端·后端·安全
FIN66687 小时前
昂瑞微:实现精准突破,攻坚射频“卡脖子”难题
前端·人工智能·安全·前端框架·信息与通信
椎4957 小时前
苍穹外卖前端nginx错误之一解决
运维·前端·nginx
@。1247 小时前
对于灰度发布(金丝雀发布)的了解
开发语言·前端