如何对无返回值但修改变量的函数开展单元测试?Jest实践
无返回值修改变量的前端函数单元测试实现指南
很多前端开发者在落地单元测试时都会碰到共性问题:当被测函数没有返回任何内容、核心逻辑是通过副作用修改变量值时,到底该怎么设计测试用例?
常见思路汇总
- 思路1:在Jest的test块中执行目标函数后,直接检查对应变量的实际值是否符合预期
- 思路2:这类场景属于简单的变量赋值逻辑,不需要专门写单元测试
- 思路3:为了方便测试,特意重构函数逻辑让它具备返回值
- 思路4:无返回值的函数本身就不应该存在,直接把逻辑合并到调用它的父函数中即可
各思路的实际适用性评估
- 思路1是这类场景下的最优解,完全契合单元测试的核心逻辑——单元测试本质是验证函数的可观测行为,函数无返回值不代表没有行为,通过副作用修改的全局/模块作用域变量,就是非常明确的可观测行为
- 思路2有一定合理性,但对于刚接触单元测试的开发者来说,很难精准划分哪些简单逻辑可以跳过测试,还是需要先梳理清楚自己项目的代码测试边界
- 思路3属于典型的过度设计,为了测试便利强行修改业务逻辑的设计,会不必要地增加代码的耦合度和维护成本
- 思路4不具备通用性,很多时候拆分独立的无返回值函数,就是为了提升整体代码的可读性,硬把初始化逻辑塞回父函数,反而会造成父函数逻辑臃肿、可读性下降
实战案例:井字棋项目的初始化函数测试
具体场景:
initializeGame函数在playGame的文档就绪回调中被调用,本身没有返回值,核心作用是设置游戏的全部初始状态:比如将当前执子玩家设置为X、清空棋盘、重置对局追踪列表gameTracker为空数组、更新页面提示文本等。本次测试的核心目标是验证执行完initializeGame后,gameTracker会被正确重置为[]。
对应的业务实现代码如下:
let gameTracker = [], turn; const turnX = 'X', turnO = 'O'; function playGame() { $(document).ready(function () { initializeGame(); $('.square').click(function () { makeMove($(this)); endTurn(); }); }); } function initializeGame() { $('.start').css('display', 'none'); $('.square').children('p').text(''); gameTracker = []; turn = turnX; $('h1').text('Player ' + turn + ' Turn'); }
你初步编写的测试用例存在两个常见的落地问题:
- 作用域访问问题:
gameTracker是模块内部的私有变量,如果没有通过module.exports导出,测试文件中是无法直接访问到这个变量的,直接在测试里写expect(gameTracker)会抛出未定义的错误,需要先把需要校验的变量暴露到模块导出对象上 - 断言匹配问题:校验数组类结构时不能使用
toBe,因为toBe做的是严格的引用相等判断,两个独立的空数组在内存中的引用地址完全不同,永远无法匹配;需要换成toEqual做深度值的匹配校验
- 写断言的时候可以提前人为给
gameTracker赋一个非空的测试值,再执行initializeGame,避免测试环境默认变量为空值带来的“假通过”问题。
调整后的参考测试代码:
const tictactoe = require('./tictactoe'); test('gameTracker should reset to empty array after running initializeGame()', () => { // 提前设置非空的初始值,避免测试假通过 tictactoe.gameTracker = ['X', 'O']; tictactoe.initializeGame(); expect(tictactoe.gameTracker).toEqual([]); })
内容的提问来源于stack exchange,提问作者ezeYaniv
相关产品推荐
相关产品推荐

