Chai 中文文档 下载 App

Chai vs Jest expect 对比 - 该选哪个断言库

一句话结论

背景

Jest 是 Facebook 开源的一体化测试框架——它把测试运行器 + 断言库 + Mock + 快照测试 + 覆盖率工具打包在一起。jest.expect 是 Jest 内置的断言接口,风格和 Chai 的 .not / .deep 很像。

这就导致一个常见问题:如果我用 Jest,还有必要用 Chai 吗?

风格对比

链式 BDD vs. 方法调用

// Chai(expect 风格)—— 链式
expect('hello').to.be.a('string');
expect(1 + 1).to.equal(2);
expect({ a: 1 }).to.deep.equal({ a: 1 });

// Jest expect —— 方法调用
expect('hello').toBeInstanceOf(String);
expect(1 + 1).toBe(2);
expect({ a: 1 }).toEqual({ a: 1 });

Jest expect 没有链式语言链(.to.be.a. 这种),每个断言是一个独立方法调用。风格差异主要是:

否定、深度、包含

三个最常用的修饰器对比:

语义Chai expectJest expect
严格相等.toEqual().toBe()
深度相等.deep.equal().toEqual()
否定.not.equal().not.toBe()
包含元素.include().toContain()
子集包含.include.members()无直接对应
键集合匹配.all.keys().toHaveProperty() 系列

Chai 在集合/对象维度更丰富(.members / .any.keys / .deep.property),Jest expect 以简洁直接为设计目标。

Jest expect 独有的能力

Jest expect 有一些 Chai 核心没有(或需要插件)的能力:

快照测试

// Jest expect —— 快照
expect(tree).toMatchSnapshot();

Chai 核心不支持快照(有第三方插件 chai-match-snapshot,但不如 Jest 内置的成熟)。

Mock 断言

// Jest expect —— 直接断言 mock 调用
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
expect(mockFn).toHaveBeenCalledTimes(3);

Chai 没有内置 mock,需要装 sinon-chai 插件(.called / .calledWith / .calledOnce)。

异步值

// Jest expect —— 原生支持 Promise
await expect(fetchData()).resolves.toBe('data');
await expect(fetchData()).rejects.toThrow();

Chai 需要 chai-as-promised 插件才能做到类似的 .eventually / .rejectedWith。

数组长度断言

// Jest expect —— 简洁
expect(items).toHaveLength(5);

// Chai
expect(items).to.have.lengthOf(5);

Jest 风格略短。

类型检查

// Jest expect —— 丰富的类型匹配器
expect(123).toBeGreaterThan(100);
expect(5).toBeLessThanOrEqual(10);
expect(value).toBeNumber();     // 非严格类型匹配
expect(value).toBeInstanceOf(Error);

Chai 独有的优势

三种风格任选

Chai 有 expect / should / assert 三种风格,Jest 只有 expect 风格。如果你或团队偏好 should 或 assert,Chai 更灵活。

框架无关

Chai 不绑定任何测试运行器。可以在 Mocha、Vitest、Karma、TAP、任何自定义运行器中使用。Jest expect 虽然可以脱离 Jest 使用,但它和 Jest 生态(mock、snapshot、coverage)的集成更紧密。

插件生态更丰富

Chai 有超过 200 个插件:chai-as-promised、chai-http、chai-dom、chai-subset、chai-bignumber…… 场景覆盖更广。Jest expect 的插件(extend-expect)数量少很多。

对象属性链访问

// Chai —— 原生支持属性路径
expect(user).to.have.deep.property('profile.settings.theme', 'dark');

// Jest expect —— 需要手动 get 或用 expect.objectContaining
expect(user.profile.settings.theme).toBe('dark');
// 或
expect(user).toEqual(expect.objectContaining({
  profile: expect.objectContaining({
    settings: expect.objectContaining({ theme: 'dark' })
  })
}));

属性路径在嵌套对象测试中更简洁。

TDD assert 风格

// Chai assert 风格 —— Node assert 的超集
const assert = require('chai').assert;
assert.deepEqual(a, b);
assert.throw(fn, ErrorType, 'message');

Jest 没有 assert 风格接口,只有 expect 风格。

技术栈推荐

测试运行器推荐断言库理由
Jestjest expect原生集成最紧密,快照/mock 一站式
Vitestvitest expect(Jest 兼容 API)风格一致,Vite 生态
MochaChai最成熟、插件最多
AvaAva 内置 assert(可用 Chai)Ava 内置 assert 够用,要更多装 Chai
KarmaChaiKarma 常与 Mocha + Chai 组合
自定义Chai框架无关
Node 脚本(非正式)Node 内置 assert(最简单)无需额外依赖

能否混用

能但不推荐——同一个测试文件里混用 Chai expect 和 Jest expect 会造成:

项目内统一一种就好。

迁移场景

Jest → Chai(少见)

// Jest expect
expect(result).toEqual(expected);
expect(mockFn).toHaveBeenCalled();

// Chai(+ sinon-chai 插件)
expect(result).to.deep.equal(expected);
expect(mockFn).to.have.been.called();

Chai → Jest expect(Mocha 项目迁移到 Jest)

// Chai expect
expect({ a: 1 }).to.deep.equal({ a: 1 });
expect(arr).to.include.members([1, 2]);

// Jest expect
expect({ a: 1 }).toEqual({ a: 1 });
expect(arr).toEqual(expect.arrayContaining([1, 2]));

注意 .include.members 在 Jest expect 里用 expect.arrayContaining 替代。

最终建议

选哪个取决于你用的测试运行器:

项目内统一一种断言风格,不要混用。

下一步