Chai 断言库速查表 - Chai.js 测试断言 expect/should/assert 大全

这张速查给用 Mocha、Jest 集成或纯 Node 写 JavaScript 测试的开发者。Chai 提供了 expect、should、assert 三种可互换的风格,这张表会把同一种检查用不同写法各展示一遍。内容覆盖相等与对象/数组的深度比较、类型与包含检查、抛错断言、数值区间,以及依赖插件的助手方法,比如 eventually(chai-as-promised)和 HTTP 状态码(chai-http)。读完你应能对值、集合、抛错和 Promise 写出清晰易读的断言,并明白什么时候该用 deep.equal 而不是 equal。

编程语言·共 44 条命令·最后更新 2026-07-21

典型使用场景

面向用 BDD 风格描述业务规则的前端与 Node.js 测试作者。三种断言风格里 expect 最通用、读起来最接近自然语言,should 写起来最短但对空值有坑,assert 属于 TDD 风格且函数式调用更显式。本表把这三种风格并列收录,让你在同一个仓库里遇到哪种都能读懂;重点覆盖相等与深度比较的选择(.equal 走严格相等、.deep.equal 逐层比较)、类型与包含断言、异常断言的四种写法(按类型、按消息正则、按不抛错),以及需要插件才生效的 Promise 与 HTTP 断言。排障部分解释了 ? 为什么显示为 ??、.own.property 与 .property 的区别、should 遇到 null 崩溃等真实会中断测试套件的问题。

Expect 风格(BDD) 8

expect(value).to.equal(expected)
相等断言(严格 ===)
expect(value).to.be.a("string")
类型断言
expect(array).to.include(item)
包含断言
expect(value).to.be.true
布尔断言
expect(value).to.not.be.null
非空断言
expect(array).to.have.lengthOf(3)
长度断言
expect(value).to.exist
存在断言(非 null 且非 undefined)
expect(obj).to.have.property("key", "value")
属性及其值断言

Should 风格(BDD) 5

value.should.equal(expected)
相等断言
value.should.be.a("string")
类型断言
array.should.include(item)
包含断言
value.should.be.true
布尔断言
value.should.exist
存在断言

Assert 风格(TDD) 7

assert.equal(actual, expected)
用宽松 == 判断相等,比较字符串/数字时够用
assert.notEqual(actual, expected)
断言两值不相等,宽松比较
assert.deepEqual(actual, expected)
递归比较对象/数组的每一层内容是否一致
assert.isTrue(value)
断言该值严格等于 true
assert.isArray(value)
断言该值是数组类型
assert.throws(fn)
断言执行 fn 会抛出异常,适合测错误分支
assert.fail(actual, expected, "message")
无条件让测试失败,常用于断言不支持到达的分支

相等与深度比较 6

expect(value).to.deep.equal(obj)
深度比较对象/数组
expect(value).to.eql(obj)
deep.equal 别名
expect(value).to.not.equal(other)
不相等断言
expect(obj).to.have.own.property("key")
自有属性断言(不含原型链)
expect(obj).to.have.nested.property("a.b.c")
嵌套属性断言
expect(arr).to.deep.equal([1, 2, 3])
数组深度比较

类型与包含断言 6

expect(value).to.be.an("array")
数组类型断言
expect(value).to.be.an.instanceof(Date)
实例类型断言
expect(str).to.match(/^foo/)
正则匹配断言
expect(str).to.have.string("bar")
字符串包含子串
expect(obj).to.include({ key: "value" })
对象部分包含
expect(arr).to.have.members([1, 2, 3])
数组成员完全相等

异常与抛错 6

expect(fn).to.throw(Error)
抛出指定类型异常
expect(fn).to.throw("error message")
抛出含指定消息异常
expect(fn).to.throw(/message/)
正则匹配异常消息
expect(fn).to.not.throw()
不抛出异常
assert.throws(fn, TypeError)
TDD 抛错断言
assert.doesNotThrow(fn)
TDD 不抛错断言

数字、布尔与插件 6

expect(num).to.be.above(5)
大于断言
expect(num).to.be.at.least(5)
大于等于断言
expect(num).to.be.below(10)
小于断言
expect(num).to.be.within(1, 10)
区间断言(含边界)
expect(promise).to.eventually.equal("resolved")
Promise 断言(需 chai-as-promised)
expect(res).to.have.status(200)
HTTP 状态断言(需 chai-http)

参数矩阵

参数作用示例
expect(value).to.equal(x)严格相等断言(===)expect(value).to.equal(expected)
expect(value).to.be.a("string")类型断言expect(value).to.be.a("string")
expect(arr).to.include(item)包含某个元素断言expect(array).to.include(item)
expect(value).to.be.true布尔真值断言expect(value).to.be.true
expect(value).to.not.be.null非空断言expect(value).to.not.be.null
expect(arr).to.have.lengthOf(3)长度断言expect(array).to.have.lengthOf(3)
expect(value).to.exist存在断言(非 null 且非 undefined)expect(value).to.exist
expect(obj).to.have.property("k", "v")断言属性存在且值匹配expect(obj).to.have.property("key", "value")
value.should.equal(x)should 风格相等断言value.should.equal(expected)
value.should.existshould 风格存在断言value.should.exist
assert.equal(a, b)宽松相等比较(==)assert.equal(actual, expected)
assert.notEqual(a, b)宽松不等断言assert.notEqual(actual, expected)
assert.deepEqual(a, b)递归比较对象/数组每一层内容assert.deepEqual(actual, expected)
assert.isTrue(v) / assert.isArray(v)布尔与数组类型断言assert.isArray(value)
assert.throws(fn)断言执行时会抛异常assert.throws(fn)
assert.fail(a, b, "message")无条件让测试失败,用于不可到达分支assert.fail(actual, expected, "message")
.to.deep.equal(obj)深度比较对象/数组expect(value).to.deep.equal(obj)
.to.eql(obj).deep.equal 的别名expect(value).to.eql(obj)
.to.not.equal(other)不相等断言expect(value).to.not.equal(other)
.to.have.own.property("k")自有属性断言,不含原型链expect(obj).to.have.own.property("key")
.to.have.nested.property("a.b.c")嵌套路径属性断言expect(obj).to.have.nested.property("a.b.c")
.to.be.an("array")数组类型断言expect(value).to.be.an("array")
.to.be.an.instanceof(Date)实例类型断言expect(value).to.be.an.instanceof(Date)
.to.match(/^foo/)正则匹配断言expect(str).to.match(/^foo/)
.to.have.string("bar")字符串包含子串断言expect(str).to.have.string("bar")
.to.include({ key: "value" })对象部分包含断言expect(obj).to.include({ key: "value" })
.to.have.members([1, 2, 3])数组成员完全相等断言expect(arr).to.have.members([1, 2, 3])
.to.throw(Error)断言抛出指定类型异常expect(fn).to.throw(Error)
.to.throw("error message")断言异常消息包含该子串expect(fn).to.throw("error message")
.to.throw(/message/)用正则匹配异常消息expect(fn).to.throw(/message/)
.to.not.throw()断言不抛异常expect(fn).to.not.throw()
assert.throws(fn, TypeError)TDD 风格抛错断言assert.throws(fn, TypeError)
assert.doesNotThrow(fn)TDD 风格不抛错断言assert.doesNotThrow(fn)
.to.be.above(5) / .to.be.below(10)大于 / 小于断言expect(num).to.be.above(5)
.to.be.at.least(5)大于等于断言expect(num).to.be.at.least(5)
.to.be.within(1, 10)区间断言,含边界expect(num).to.be.within(1, 10)
.to.eventually.equal("resolved")Promise 断言,需 chai-as-promised 插件expect(promise).to.eventually.equal("resolved")
.to.have.status(200)HTTP 状态断言,需 chai-http 插件expect(res).to.have.status(200)

易错点与避坑指南

现象对可能是 null 的值用 should 风格断言,测试直接崩在读 .should 上,套件中断。

原因should 写法需要先对对象取属性,null/undefined 在断言执行前就抛错。

处置改用 expect 风格:expect(value).to.not.be.null,断言函数会先安全地接收空值。

现象对象明明字段值一致,.to.equal 却失败。

原因.equal 走的是严格相等,只对引用类型比较地址,两个内容相同但不同引用的对象不会通过。

处置改用 .to.deep.equal 或 .eql 逐层比较内容。

现象数组断言「看起来是同一个数组」却依然失败。

原因严格相等比较的是引用,测试里重新构造的数组与被测对象里的数组不是同一个引用。

处置用 expect(arr).to.deep.equal([1, 2, 3]) 做内容比较,或用 .have.members 只比较成员集合。

现象想断言「对象上有这个属性」,结果原型链上的属性也算通过。

原因.property 会遍历原型链,而判断自有属性应该用 .own.property。

处置改用 expect(obj).to.have.own.property("key"),只认对象自身的属性。

现象报错信息里的 expected 是 ??,看不出到底在比什么。

原因传给期望值的表达式本身求出了 undefined,Chai 只能把 undefined 渲染成占位符。

处置把期望值先算好再传入,或给断言补上自定义 message,确保比较的是确定的值。

现象用 .to.throw() 断言时函数其实抛了另一个错,却仍然通过。

原因不传参数时只校验「有没有抛错」,不校验类型和消息。

处置给出具体期望:.to.throw(Error) 校验类型,.to.throw("message") 或 .to.throw(/message/) 校验消息内容。

现象断言 Promise 解析结果时报 eventually is not a function。

原因Promise 相关断言依赖 chai-as-promised 插件,插件未注册。

处置在测试入口注册 chai-as-promised 后再使用 expect(promise).to.eventually 等链式写法。

排障路径

  1. 1先确认值的实际类型再选断言

    expect(value).to.be.a("string")

    类型断言失败时会直接打印实际类型,比猜断言写法更快定位问题。

  2. 2区分浅比较与深比较

    expect(value).to.deep.equal(obj)

    内容一致但 .to.equal 失败,就说明应该换成深比较。

  3. 3只比较数组成员而不关心顺序

    expect(arr).to.have.members([1, 2, 3])

    成员集合相同时通过,顺序不同也能通过。

  4. 4校验错误分支确实抛出了指定异常

    expect(fn).to.throw(TypeError)

    把被测函数包进断言执行,未抛出或类型不符都会直接失败并打印实际错误。

  5. 5确认属性是自有属性而非原型链继承

    expect(obj).to.have.own.property("key")

    若 .property 通过而 .own.property 失败,说明该属性来自原型链。

  6. 6断言 HTTP 响应状态

    expect(res).to.have.status(200)

    需先注册 chai-http 插件;失败时输出实际状态码。

命令示例

BDD 三段式断言:类型 + 内容 + 空值一次写全

const res = await parse(body); expect(res).to.be.an("object"); expect(res.data).to.have.lengthOf(3); expect(res.error).to.not.exist; expect(res.data[0].name).to.equal("tom");

Chai 的 BDD 风格是链式的,expect 只写一次,后面 .to / .be / .have 都可以换着用,断言信息会自动拼成可读的路径。比分别写四条断言更容易看出「哪个属性出的问题」。

深度比较对象/数组:deep.equal 与 eql

expect(actual).to.deep.equal({ id: 1, tags: ["a", "b"] }); // eql 是 deep.equal 的别名,风格更短 expect(rows).to.eql([{ id: 1 }, { id: 2 }]); // 数组要求成员完全一致,顺序无关 expect(ids).to.have.members([3, 1, 2]);

equal 是严格 ===,两个内容相同但引用不同的对象一定失败;比结构必须用 deep.equal / eql。members 则要求集合成员完全相同(可乱序),适合断言「恰好这几条,没有多余项」。

测试错误分支:throws 的三种匹配方式

expect(() => JSON.parse("{")).to.throw(); expect(() => parse("x")).to.throw(TypeError); expect(() => parse("x")).to.throw(/invalid/); // 不该抛异常的分支也要断言,否则静默通过 expect(() => parse("{}")).to.not.throw();

只写 to.throw() 只能证明「抛了东西」,写错错误类型或错误消息的 bug 一样能过。按「异常类型 + 消息正则」收紧,并在成功路径补一条 to.not.throw(),才能真正覆盖双向分支。

只断言对象里关心的几个字段:include

expect(user).to.include({ role: "admin" }); expect(user).to.have.own.property("id"); expect(user).to.have.nested.property("profile.address.city");

include 做部分包含匹配,新增字段不会让测试红;own.property 只看自有属性,继承来的也算「存在」会误判;nested.property 用点号走嵌套路径,任一层缺失都会给出明确报错位置。

数字区间与异步:within / eventually / status

expect(score).to.be.within(0, 100); expect(save()).to.eventually.equal("ok"); // 需先注册 chai-as-promised / chai-http // expect(res).to.have.status(200);

within 两端都含边界,避免手写 above(0).and.below(100) 漏掉边界值。eventually / status 依赖 chai-as-promised、chai-http 这类插件,不注册就直接报找不到断言方法。

常见坑与注意事项

  • expect(value).to.equal(other) 用的是严格 ===,两个内容相同但引用不同的对象/数组一定失败;比结构时忘记改成 deep.equal,会出现「明明一样却不通过」的假故障。
  • BDD 链式断言一旦中间某个属性为 undefined,后面的 .to.have.property 会连带炸出一串无关报错。排查时从链首开始逐段缩小,不要一次改多处。
  • assert.equal 用宽松 == 比较,"1" 与 1 会被判为相等,字符串与数字混用的字段用它会漏掉类型错误;优先用严格相等的断言。
  • expect(fn).to.throw() 只验证「抛了异常」,不校验类型和消息,业务抛错类型写错也能测试通过——错误分支必须同时约束类型与消息。
  • Promise 断言必须先注册 chai-as-promised,否则 eventually / rejected 这些属性不存在,报错是「断言方法不是函数」,与业务代码无关,容易误判为测试环境问题。
  • 只写 expect(fn).to.throw() 而不给成功路径补一条「不抛异常」的断言时,出错分支被改坏(该抛的不抛)完全不会被测出来。

提示

  • expect 和 should 是 BDD 风格,assert 是 TDD 风格。
  • should 需要对对象调用 .should,可能对 null/undefined 报错。
  • 深度比较用 .deep.equal,浅比较用 .equal。
  • chai-as-promised 插件支持 Promise 断言,chai-http 支持 HTTP 请求断言。
  • 链式方法如 to、be、have 仅提高可读性,无实际功能。

常见问题

什么时候该选 should,什么时候该选 expect?

expect(value).to.equal(expected) 把被测对象作为第一个参数传入,断言写在后面;should 风格是 value.should.be.a("string"),断言挂在被测对象上。差别有两点:一是代码风格,BDD 读起来更顺,expect 写起来对 undefined/null 更安全;二是空值处理——should 是给对象加属性,对 null 和 undefined 调用 .should 会直接报错,所以断言可空值时必须用 expect(value).to.not.be.null 这种 expect 形式。assert 属于 TDD 风格,写作 assert.equal(actual, expected),更接近单元测试框架的经典形态。一个项目里选定一种并保持一致即可。

equal 和 deep.equal 到底差在哪一层?

expect(value).to.equal(expected) 是严格相等(===),比较的是引用或原始值:expect([1,2]).to.equal([1,2]) 会失败,因为这是两个不同的数组对象。expect(value).to.deep.equal(obj)(简写 .to.eql)才逐层递归比较内容,数组、嵌套对象都能匹配。另外 expect(obj).to.have.own.property("key") 只看自有属性、不查原型链,expect(obj).to.have.nested.property("a.b.c") 支持路径式断言。排查「明明内容一样却断言失败」时,先确认是不是漏了 deep——这是最常见的原因。

测「错误分支」时,怎么断言一个函数确实抛了错?

expect 风格有四种粒度:expect(fn).to.throw(Error) 只看异常类型,expect(fn).to.throw("error message") 要求消息全等,expect(fn).to.throw(/message/) 用正则匹配消息的一部分,expect(fn).to.not.throw() 断言正常路径。关键点是必须传函数本身而不是调用结果——写成 expect(fn()).to.throw() 会先执行 fn,异常在断言之前就抛出去了。assert 风格对应 assert.throws(fn, TypeError) 和 assert.doesNotThrow(fn)。断言不支持到达的分支时用 assert.fail(actual, expected, "message") 让测试无条件失败,比悄悄通过更安全。

测试异步接口返回的 Promise,chai 要怎么配?

chai 核心不含异步断言,需要插件。最常用的是 chai-as-promised,它给 expect 加上了 eventually 修饰:expect(promise).to.eventually.equal("resolved") 会等 Promise 落定后再断言结果,配合 rejected 则用于断言失败原因,形式类似 expect(promise).to.be.rejectedWith(Error)。HTTP 状态断言则由 chai-http 提供,expect(res).to.have.status(200) 直接校验响应码和响应体。若项目已使用 Jest 的 expect,这两类断言通常由 Jest 自带的 resolves / rejects 覆盖,无需再引 chai 插件。

include 和 members 断言数组,结果差在哪?

expect(arr).to.include(item) 只要求数组中存在该元素,多余元素不影响判定,适合「至少包含用户要找的那一项」。expect(arr).to.have.members([1, 2, 3]) 要求成员完全一致、个数和值都要对得上,适合快照式的整体校验。另外 expect(obj).to.include({ key: "value" }) 是对象部分包含,只比对指定键,不要求对象整体相等。字符串有专门的 expect(str).to.have.string("bar") 做子串匹配,而数字比较则用 above / below / at.least / within(1, 10) 这组。选错会得到过松或过严的断言。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们