assert 断言风格详解 - TDD 经典函数式调用
基本形式
const assert = require('chai').assert;
assert.equal(actual, expected);
assert.typeOf(value, 'string');
assert.lengthOf(array, 3);
assert 风格是函数式调用——每个断言是一个独立函数,不支持链式。这与 Node.js 内置 assert 模块非常相似,但功能更丰富且跨平台。
引入方式
// CommonJS
const assert = require('chai').assert;
// ESM
import { assert } from 'chai';
三种风格对比:同一个断言的写法
// 被断言的数据
const foo = 'bar';
const beverages = { tea: ['chai', 'matcha', 'oolong'] };
// expect 风格(BDD 链式)
const { expect } = require('chai');
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(beverages).to.have.property('tea').with.lengthOf(3);
// should 风格(BDD 原型注入)
require('chai').should();
foo.should.be.a('string');
foo.should.equal('bar');
beverages.should.have.property('tea').with.lengthOf(3);
// assert 风格(TDD 函数式)
const assert = require('chai').assert;
assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.property(beverages, 'tea');
assert.lengthOf(beverages.tea, 3);
assert 风格的调用方式是 assert.方法(actual, expected, [message])——把「实际值」作为第一个参数,「期望值」作为第二个参数。
消息参数
assert 风格支持为每个断言传入可选的消息参数(通常是最后一个参数),失败时拼接到错误信息中:
assert.typeOf(foo, 'string', 'foo 必须是字符串');
assert.equal(foo, 'bar', 'foo 的值应该是 bar');
常用 assert 方法速查
类型检查
assert.typeOf(value, 'string', [message]); // 类型(小写字符串)
assert.notTypeOf(value, 'number', [message]); // 不是某类型
assert.instanceOf(object, Class, [message]); // 原型链
assert.notInstanceOf(object, Class, [message]); // 不是原型链
值比较
assert.equal(actual, expected, [message]); // 非严格等 ==
assert.notEqual(actual, expected, [message]); // 非严格 !=
assert.strictEqual(actual, expected, [message]); // 严格 ===
assert.notStrictEqual(actual, expected, [message]); // 严格 !==
assert.deepEqual(actual, expected, [message]); // 深度相等
assert.notDeepEqual(actual, expected, [message]); // 深度不等
存在性
assert.isOk(value, [message]); // truthy(注意方法名是 isOk 不是 isTruthy)
assert.isNotOk(value, [message]); // falsy
assert.isNull(value, [message]);
assert.isNotNull(value, [message]);
assert.isUndefined(value, [message]);
assert.isDefined(value, [message]);
assert.exists(value, [message]); // 非 null 非 undefined
assert.notExists(value, [message]);
布尔
assert.isTrue(value, [message]);
assert.isFalse(value, [message]);
数字比较
assert.isAtLeast(actual, expected, [message]); // >=
assert.isAtMost(actual, expected, [message]); // <=
assert.isAbove(actual, expected, [message]); // >
assert.isBelow(actual, expected, [message]); // <
assert.isWithin(value, min, max, [message]); // 区间 [min, max]
assert.isCloseTo(actual, expected, delta, [message]); // 近似
字符串
assert.isString(value, [message]);
assert.include(str, substring, [message]);
assert.notInclude(str, substring, [message]);
assert.lengthOf(str, n, [message]);
assert.isAbove(str.length, 0, [message]); // 也可以用 lengthOf
数组
assert.isArray(value, [message]);
assert.lengthOf(array, n, [message]);
assert.include(array, element, [message]);
assert.notInclude(array, element, [message]);
assert.sameMembers(set1, set2, [message]); // 无序成员相同
assert.sameDeepMembers(set1, set2, [message]); // 深度 + 无序
assert.includeMembers(needle, haystack, [message]); // needle 是 haystack 的子集
对象
assert.isObject(value, [message]);
assert.isPlainObject(value, [message]);
assert.property(obj, key, [message]);
assert.propertyVal(obj, key, expected, [message]);
assert.notProperty(obj, key, [message]);
assert.deepPropertyVal(obj, path, expected, [message]);
assert.hasAllKeys(obj, keys, [message]);
assert.hasAnyKeys(obj, keys, [message]);
异常
assert.throw(fn, [type], [regex], [message]);
assert.doesNotThrow(fn, [type], [regex], [message]);
自己写的表达式
assert(expression, [message]); // 最基础的 truthy 断言
assert.fail([message]); // 强制失败
assert.fail(actual, expected, [message], [operator]);
深度相等与嵌套属性
assert 风格中深度相等是显式方法,不需要修饰链:
// expect / should 风格
expect({ a: 1 }).to.deep.equal({ a: 1 });
// assert 风格
assert.deepEqual({ a: 1 }, { a: 1 });
嵌套属性的路径访问:
assert.deepNestedPropertyVal(
{ tea: { green: { matcha: 'yum' } } },
'tea.green',
{ matcha: 'yum' }
);
与 Node.js 内置 assert 的关系
Chai 的 assert 风格是 Node.js 内置 assert 模块的超集——内置 assert 的方法 Chai 全有,而且 Chai 还额外提供了 .deep 系列、.members 系列、.property 系列等更多方法。
迁移代码时可以直接 const assert = require('chai').assert,不需要逐个改方法名。
什么时候选 assert 风格
assert 风格适合:
- 从 Node.js 内置 assert 迁移:API 几乎一致,替换成本低。
- 不喜欢链式调用:团队偏好函数式代码风格。
- 代码需要静态分析工具友好:链式调用在某些 linter 下会触发误报。
- 配合传统 TDD 习惯:
assert.方法(actual, expected)的形式接近 xUnit 风格。
assert 风格不太适合:
- 需要非常流畅的 BDD 自然语言表达。
- 大量使用
.deep/.any/.all等修饰器组合(assert 风格要显式调用对应方法)。
下一步
- 对比三种风格整体优缺点 → 风格对比
- 了解 should 风格的更多限制 → should 风格限制
- 全局配置选项 → 全局配置