Chai 中文文档 下载 App

Chai 常见陷阱与避坑清单 - 12 个高频错误及解决方案

总览

编号陷阱场景
1忘记 .deep 导致对象相等失败对象 / 数组值比较
2.throw 参数没传函数异常捕获
3chai-as-promised 忘记 await / return异步 Promise 断言
4should 风格忘记初始化 ()风格选择
5should 风格断言 null / undefined 直接报错should 风格
6对象字面量在 should 风格下没加括号should 风格
7异步函数传 .throw 不会捕获 Promise reject异常 + 异步
8.members 和 .includeMembers 语义混淆数组成员匹配
9过度使用 .not 导致测试意图模糊否定断言
10.exist 不是 .ok(存在性 vs 真假值)值存在性检查
11忘记 import 插件的副作用插件 ESM 引入
12链式过长导致 Linter 误报代码风格

陷阱 1:忘记 .deep

现象:expect({ a: 1 }).to.equal({ a: 1 }) 报错"expected { a: 1 } to equal { a: 1 }"

原因:Chai 默认用 === 严格相等,对象比较引用不同。

解决:加 .deep 修饰器或用 .eql() 简写:

expect({ a: 1 }).to.deep.equal({ a: 1 });
expect({ a: 1 }).to.eql({ a: 1 });  // 简写

详见:.deep 修饰器

陷阱 2:.throw 参数没传函数

现象:expect(throwError()).to.throw() 直接抛出异常,测试崩溃。

原因:.throw 需要一个函数来调用并捕获异常,直接传表达式会让 JS 立即执行。

解决:包成函数:

// ❌ 错误
expect(throwError()).to.throw();

// ✅ 正确
expect(throwError).to.throw();
expect(() => throwError()).to.throw();

详见:异常捕获

陷阱 3:chai-as-promised 忘记 await / return

现象:异步断言"静默通过"——实际断言没执行,但测试也不报错。

原因:chai-as-promised 的断言返回 Promise,不 await / return 测试就不知道要等。

解决:必须 await 或 return:

// ❌ 忘记 await
it('测试', async function () {
  expect(asyncOp()).to.eventually.equal(42);
});

// ✅ 正确
it('测试', async function () {
  await expect(asyncOp()).to.eventually.equal(42);
});

详见:chai-as-promised

陷阱 4:should 风格忘记初始化括号

现象:const should = require('chai').should 后写 foo.should... 报错"should is not a function"。

原因:没执行初始化函数。

解决:加括号执行:

// ❌ 少了 ()
const should = require('chai').should;

// ✅ 正确
const should = require('chai').should();
// 或 ESM
import 'chai/register-should';

详见:should 风格

陷阱 5:should 风格断言 null / undefined

现象:null.should.be.null 直接 TypeError。

原因:null / undefined 没有原型链,没被 should 扩展。

解决:用 should 快捷助手或混和 expect:

const should = require('chai').should();
should.be.null(null);          // 助手方式
should.not.exist(err);          // 助手方式

// 或临时切到 expect
const { expect } = require('chai');
expect(null).to.be.null;

详见:should 风格限制

陷阱 6:对象字面量在 should 风格没加括号

现象:{ name: 'Chai' }.should.have.property('name') 语法错误。

原因:JS 把 { 开头的表达式当成代码块。

解决:用括号包成表达式:

// ❌ 语法错误
{ name: 'Chai' }.should.have.property('name');

// ✅ 正确
({ name: 'Chai' }).should.have.property('name');

这条也适用于箭头函数返回对象。

陷阱 7:异步函数传 .throw

现象:expect(async () => { throw new Error() }).to.throw() 永远通过。

原因:async 函数本身不抛异常——它返回一个 reject 的 Promise。.throw 检查的是同步抛出。

解决:用 chai-as-promised 的 .rejectedWith:

// ❌ 永远通过
expect(async () => { throw new Error(); }).to.throw();

// ✅ 用 chai-as-promised
await expect(async () => { throw new Error(); }).to.be.rejectedWith(Error);

或者 async/await + try-catch。

陷阱 8:.members vs .includeMembers

现象:断言明明失败了却通过(或反过来)。

原因:两者语义不同:

expect([1, 2, 3]).to.have.members([1, 2]);          // ❌ 失败
expect([1, 2, 3]).to.includeMembers([1, 2]);        // ✅ 通过

陷阱 9:过度使用 .not

现象:测试断言太宽泛,导致 bug 被掩盖。

反模式:

// 404、500、302 都会通过
expect(res.status).to.not.equal(404);

// 'bob'、'alice123'、空字符串都可能通过
expect(name).to.not.be.null;

推荐:

expect(res.status).to.equal(200);
expect(name).to.be.a('string').and.have.length.above(0);

陷阱 10:.exist vs .ok

现象:expect(count).to.be.ok 在 count = 0 时误判失败。

原因:

0 是合法值但不是 truthy。用错了会让测试在 count = 0 时意外失败。

解决:只是确认值存在(不管真假)→ .exist;确认值是 truthy → .ok。

陷阱 11:忘记 import 插件的副作用

现象:TypeScript / ESM 中 chai.use(chaiAsPromised) 报错,或者插件断言不可用。

原因:某些 ESM 构建工具对默认导入和命名导入处理不一致。

解决:

// 方式一:默认导入 + use
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);

// 方式二:命名导入
import * as chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
chai.default.use(chaiAsPromised);

如果还是不行,看插件 README 里的 ESM 用法说明。

陷阱 12:链式过长 Linter 误报

现象:链式调用超出 ESLint max-len 限制被标记。

解决:

// 链式过长(ESLint 报错)
expect(obj).to.have.deep.property('user.profile.settings.theme').that.equals('dark');

// ✅ 拆成两句
expect(obj).to.have.deep.property('user.profile');
expect(obj.user.profile.settings.theme).to.equal('dark');

通用调试技巧

看错误消息

Chai 的错误消息包含实际值和期望值,绝大多数情况下能直接定位:

AssertionError: expected { a: 1 } to deeply equal { a: 2 }
                  ↑ 实际值              ↑ 期望值(前面的 + / - 标记差异)

加自定义消息

expect 风格支持第二参数:

const firstName = response.data.items[0].name;
expect(firstName, '第一个用户的名字').to.be.a('string');

错误消息会变成"第一个用户的名字: expected undefined to be a string"——定位到了嵌套路径返回 undefined。

开启 includeStack

chai.config.includeStack = true;

错误消息追加堆栈,帮助找到自定义 helper 里的最终调用位置。

下一步