expect vs should vs assert 三种断言风格全面对比与选择建议
一句话结论
- 新手首选 expect:零初始化、全环境兼容、功能完整。
- should 不推荐新项目:原型注入、null/undefined 痛点、兼容性问题较多。
- assert 适合特定场景:从 Node assert 迁移、不喜欢链式、xUnit 习惯团队。
六维度对比表
| 维度 | expect | should | assert |
|---|---|---|---|
| 初始化 | 无,直接 require('chai').expect | 必须 chai.should() | 无,直接 require('chai').assert |
| 原型污染 | 无 | 扩展 Object.prototype | 无 |
| null/undefined | 原生支持 | 报错,需助手/expect 混用 | 原生支持 |
| IE9 兼容 | ✅ | ❌ | ✅ |
| 链式调用 | ✅ 完整链式 | ✅ 完整链式 | ❌ 无链式 |
| 自然语言感 | 好 | 最好 | 一般(函数调用) |
| ES2015 模块 | import { expect } from 'chai' | import 'chai/register-should' | import { assert } from 'chai' |
| 深度相等 | .deep 修饰链 | .deep 修饰链 | assert.deepEqual() 独立方法 |
| 自定义消息 | expect(val, '消息') 首参第二位置 | 不支持(需用 expect) | assert.xxx(a, b, '消息') 末参 |
| 插件兼容 | 全部 | 全部 | 全部 |
功能等价性
底层完全等价——三种风格共享同一套断言实现。下表展示同一个意图的三种写法:
// 意图:验证对象 {a:1} 和预期值深度相等
const actual = { a: 1 };
const expected = { a: 1 };
// expect
expect(actual).to.deep.equal(expected);
// should
actual.should.deep.equal(expected);
// assert
assert.deepEqual(actual, expected);
链式修饰器在 expect 和 should 中都能使用,assert 风格则通过独立方法体现(assert.deepEqual 而非 assert.equal 加修饰器)。
expect 风格优缺点
优势:
- 引入即用,无需初始化步骤。
- 不修改任何内置原型,零副作用。
- 所有环境(包括 IE9)正常工作。
- null / undefined 值直接断言,没有额外心智负担。
- 第二参数传自定义消息,调试体验好。
- IDE 补全和静态分析最友好。
- 是 Chai 官方最推荐的风格。
劣势:
expect(值).to...的形式比值.should...略多几个字符。- 链式太长时 Linter 可能需要额外配置(line-break / max-len)。
should 风格优缺点
优势:
- 自然语言感最强:
foo.should.equal(bar)读起来是完整句子。 - 不需要顶部声明变量——直接在值后面
.should。 - 对 IDE 补全友好:
value.should.之后能看到所有可用断言。
劣势:
- 修改 Object.prototype:虽然实现上只加了一个 getter,不会覆盖已有属性,但在某些边缘场景(如库内部代码、严格 Object 操作)可能引发困惑。
- null / undefined 不可用:
null.should直接 TypeError。 - IE9 不兼容:
Object.defineProperty在 IE9 的实现有缺陷。 - ES2015 模块初始化:
import chai from 'chai'; chai.should();分两行不够简洁(虽然import 'chai/register-should'能解决)。 - 对象字面量加括号:
({ name: 'Chai' }).should.have.property('name')略累赘。 - 不支持自定义消息:没有
value.should.equal(x, '消息')的写法。
assert 风格优缺点
优势:
- 零迁移成本:与 Node.js 内置 assert 模块 API 几乎完全一致,替换一行就能获得更丰富的断言。
- 函数式调用:不依赖链式,对不喜欢链式风格或使用静态分析工具的团队友好。
- 无原型污染:同 expect。
- 经典 xUnit 习惯:方法名如
isTrue、isNull等与 xUnit 风格对齐。
劣势:
- 没有链式:写深度比较要显式调用
assert.deepEqual,不能加修饰器。 - 可读性略差:
assert.typeOf(foo, 'string')不如expect(foo).to.be.a('string')自然。 - 方法名较多:
equal / strictEqual / deepEqual、isTrue / isNull / isUndefined需要记住更多方法。 - 修饰链缺失:
.any/.all/.includeMembers这类链式逻辑在 assert 中对应方法名不够直观。
团队内统一风格
一个团队内选一种风格并保持一致,不要混用。
常见做法:
- 在
.mocharc.js/jest.config.js的 setup 阶段全局注册所选风格。 - ESLint 中加一条规则:禁止其他风格的引入语句。
// .mocharc.js - 全局注册 should
module.exports = {
require: ['chai/register-should']
};
何时考虑切换风格
- 项目已有大量 Node.js 内置 assert → 切换到 chai assert 风格,成本最低。
- 新项目没有历史包袱 → 选 expect。
- 团队偏爱 should 的自然语言感 → 可以选 should,但要统一处理 null/undefined 的策略。
结论
大多数项目,expect 是最安全的选择。它没有明显短板,所有场景都能处理好,且是官方推荐。should 虽然读起来很顺,但原型注入和 null 处理让它有一些绕不开的痛点。assert 主要面向特定迁移场景。
下一步
- should 风格的更多限制细节 → should 风格限制
- Chai 全局配置 → 全局配置
- 语言链完整列表 → 语言链总览