使用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
    • 设计测试用例
    • 写测试,运行测试,改代码
  • 单元测试、功能测试、集成测试的区别

    • 单元测试不应该与外界打交道(那是集成测试要做的)
    • 单元测试的对象是函数
    • 功能测试的对象是模块
    • 集成测试的对象是系统
    • 一名优秀的开发者应该优先保证能做到单元测试
相关推荐
Mr_Xuhhh4 分钟前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
永乐春秋1 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿1 小时前
【前端】CSS
前端·css
ggdpzhk1 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
学不会•3 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜6 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点6 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow6 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o6 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic7 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端