Chai 中文文档 下载 App

should 断言风格详解 - 原型注入 BDD 链式

基本形式

require('chai').should();
foo.should.be.a('string');
foo.should.equal('bar');

与 expect 的核心区别:expect 是函数式入口 expect(value),should 是原型注入入口 value.should。

初始化

should 风格需要显式调用初始化函数(注意是执行函数,不是引用):

const should = require('chai').should();

容易犯的错:忘记加括号 ():

// ❌ 这样不会生效——你只是拿到了函数的引用,没执行它
const should = require('chai').should;

// ✅ 正确
const should = require('chai').should();

ES2015 模块下的写法:

import chai from 'chai';
chai.should();

// 或者更简洁的一行
import 'chai/register-should';

语法对比 expect

两者底层完全相同,只是入口不同:

// expect 风格
expect('hello').to.be.a('string');
expect([1, 2, 3]).to.have.lengthOf(3);
expect({ name: 'Chai' }).to.have.property('name');

// should 风格(去掉外层 expect(value),改成 value.should)
'hello'.should.be.a('string');
[1, 2, 3].should.have.lengthOf(3);
({ name: 'Chai' }).should.have.property('name');

注意对象字面量需要用括号包起来,否则 JS 会把花括号当成代码块:

// ❌ 语法错误——JS 把 { name: 'Chai' } 解析成代码块
{ name: 'Chai' }.should.have.property('name');

// ✅ 用括号包起来
({ name: 'Chai' }).should.have.property('name');

should 风格独有的快捷助手

由于 should 扩展了 Object.prototype,它也提供了几个全局快捷助手:

const should = require('chai').should();

// 这些可以直接用在 null / undefined 场景(后面会讲为什么需要)
should.not.exist(err);
should.exist(user);
should.equal(result.statusCode, 200);
should.throw(() => undefined.foo());

这些助手在处理可能为 null / undefined 的值时特别有用——因为 null 和 undefined 不会被扩展原型。

null / undefined 的处理痛点

should 风格有一个绕不开的问题:null 和 undefined 没有原型,所以 null.should 会直接报错:

// ❌ TypeError: Cannot read properties of null (reading 'should')
null.should.be.null;
undefined.should.be.undefined;

解决方案有两个:

方案一:用 should 快捷助手

const should = require('chai').should();
should.be.null(null);
should.be.undefined(undefined);
should.not.exist(err);
should.exist(user);

方案二:混入 expect 风格

const { expect } = require('chai');
const should = require('chai').should();

// 大部分情况用 should
foo.should.be.a('string');

// 遇到 null / undefined 场景切换到 expect
expect(err).to.not.exist;

团队内可以约定一条规则:断言 null / undefined 时用 expect,其他情况用 should。

初始化时机

should 只需要初始化一次。在 Mocha 的 .mocharc.js 或 Jest 的 setup 文件里统一初始化:

// .mocharc.js
module.exports = {
  require: ['chai/register-should']
};

这样所有测试文件自动生效,不用每个文件单独写 require('chai').should()。

与 expect 共享的 API

除了入口不同,should 和 expect 底层共享所有断言方法和修饰器。.deep、.not、.include、.property 等用法完全一致。

链式结构对比:

// expect
expect(obj).to.deep.include({ key: 'val' });

// should
obj.should.deep.include({ key: 'val' });

语言链 to、be、a、have 等在 should 中同样有效。

ES2015 模板字符串的陷阱

模板字符串如果只包含一个表达式,结果就是这个表达式本身:

const x = 'hello';
`${x}` === 'hello';  // true
`${x}`.should.be.a('string');  // 合法,但不太常见

这没什么问题,知道就行。

什么时候选 should

should 风格的优势:

should 风格的劣势:

与类型系统的配合

TypeScript 中 should 风格需要类型声明文件的支持。Chai v4+ 自带了完整的类型声明,安装后 TypeScript 能正确识别 .should 扩展的属性。

下一步