Node.js 深比较测试:字段一样,为什么类实例和普通对象仍然不相等
测试里实际值是Ticket实例,期望值写成一个带id的普通对象,两边打印出来几乎一样,严格深比较却失败。把断言改成宽松版本能让测试变绿,但可能同时放过数值类型错误。更可靠的做法是先写清要比较的是完整对象语义,还是某个明确的数据传输结构。
本例在Linux、Node.js v24.19.0实跑,使用.mjs文件与内置模块,无需安装第三方包。所有数据都在内存中构造,不访问网络、不读取凭据,也不修改外部服务。文档以官方说明核对,输出来自这里标明的具体运行版本。
AI生成的概念示意图:两张正面图案相同的对象卡片放在不同形状底座上,旁边有一张只抽取共同字段的新卡片;不是真实软件界面或运行截图。
完整程序与实际输出
保存为demo.mjs,执行node demo.mjs。代码与输出分列如下。
import legacyAssert from 'node:assert';
import strictAssert from 'node:assert/strict';
class Ticket {
constructor(id) { this.id = id; }
}
const actual = new Ticket(7);
const plain = { id: 7 };
function outcome(label, check) {
try { check(); console.log(label, 'passed'); }
catch (error) {
if (error.code !== 'ERR_ASSERTION') throw error;
console.log(label, 'failed');
}
}
outcome('legacy deepEqual prototype', () => legacyAssert.deepEqual(actual, plain));
outcome('deepStrictEqual prototype', () => strictAssert.deepStrictEqual(actual, plain));
outcome('strict import deepEqual', () => strictAssert.deepEqual(actual, plain));
outcome('projected DTO', () => strictAssert.deepStrictEqual({ id: actual.id }, plain));
outcome('legacy numeric string', () => legacyAssert.deepEqual({ id: 7 }, { id: '7' }));
outcome('strict numeric string', () => strictAssert.deepEqual({ id: 7 }, { id: '7' }));
strictAssert.notEqual(Object.getPrototypeOf(actual), Object.getPrototypeOf(plain));本次实际标准输出:
legacy deepEqual prototype passed deepStrictEqual prototype failed strict import deepEqual failed projected DTO passed legacy numeric string passed strict numeric string failed
相同自有字段还可能有不同原型
Ticket构造器把id设为七,plain也有同名同值的字段。旧模式deepEqual通过,deepStrictEqual失败;最后的原型断言进一步证明两者确实来自不同原型。严格比较把这种差异纳入合同,适合需要确认函数仍返回指定类实例的测试。
类实例可能拥有普通对象没有的方法或约束,即使当前可枚举字段一样,调用方可用的行为也可能不同。因此遇到这类失败时,先检查接口承诺返回什么。若实现意外丢失了类身份,测试失败可能正是在揭示真实回归,不能仅凭打印内容相似就放宽规则。
同名deepEqual取决于导入入口
程序同时导入node:assert和node:assert/strict,并使用不同变量名避免混淆。strict import deepEqual同样失败,因为严格入口下的deepEqual采用严格深比较语义。只看调用末尾的方法名,无法可靠判断测试正在执行哪一种规则。
审查旧测试时,应把import和断言调用一起看。迁移到严格入口可能暴露此前被忽略的原型或类型差异,需要逐个判断预期值是否正确。不要为了恢复通过率,在局部文件偷偷改回旧入口,这会让同一项目里的测试合同变得难以预测。
只关心数据时,显式投影字段
projected DTO通过,因为程序从actual明确构造了一个只含id的普通对象,再与期望数据比较。这个动作表达了“这里验证的是导出的id字段”。它与放松整个深比较规则不同,后面的值仍按严格方式检查,数字与字符串不会被混为一谈。
投影只应包含当前接口真正承诺的字段。如果接口要求返回完整对象,随意挑选少数字段会漏掉多余或缺失内容。较好的实践是围绕专门的数据导出函数测试其完整结果,并对类实例本身另外检查身份和行为,让两种要求分别可见。
宽松比较可能一起放过类型错误
最后两组把数字七与字符串七放进相同字段。旧模式通过,严格模式失败,这正是全局切换比较方式可能带来的额外影响。本例选择小而清晰的值,不讨论所有内置容器、循环对象或特殊属性;更复杂类型应查对应版本的比较规则。
outcome只把预期的断言错误转成passed或failed标签,遇到其它异常仍抛出,避免把程序自身出错伪装成比较失败。正式测试通常让断言直接失败并保留差异信息。最终目标是让测试准确描述接口,不是让任何看起来相似的对象都能通过。
参考资料与验证记录
官方资料核验于2026年10月3日。本次完整程序退出码为0,标准错误为空。


