Chai 中文文档 下载 App

.not 否定修饰器 - 链式中插入 .not 反转断言结果

基本用法

在链式中任何位置插入 .not,会否定它之后的所有断言:

expect(4).to.not.equal(5);            // 通过:4 不等于 5
expect({}).to.not.have.property('name');  // 通过:空对象没有 name
expect([1, 2]).to.not.include(3);     // 通过:数组不包含 3

.not 影响的是链式中它之后的部分,它之前的不受影响:

expect([1, 2, 3]).to.be.an('array').that.does.not.include(4);
//                 ↑ 不受 not 影响  ↑ 受 not 影响

嵌套 .not

理论上可以嵌套多个 .not,每多一个相当于翻转一次:

expect(2).to.not.not.equal(2);  // 等同于 expect(2).to.equal(2)

但这种写法极度不推荐——读起来像绕口令,毫无意义。.not.not 在代码评审中应该被直接打回。

推荐 vs 不推荐用法

官方文档反复强调一个原则:推荐断言"是什么",而不是断言"不是什么"。

// ✅ 推荐:断言你期望的结果
expect(response.status).to.equal(200);
expect(result.type).to.be.a('User');
expect(form.isValid).to.be.true;

// ❌ 不推荐:断言"不是某个值"——这并不能保证是你想要的结果
expect(response.status).to.not.equal(500);  // 也可能是 404
expect(result.type).to.not.be.Null;          // 也可能是 string

原因:

  1. .not 匹配的空间更大——"不等于 500"可以是 200、404、302、任意值,只有一种是正确的。
  2. 失败时 .not 给出的错误信息不够有信息量。
  3. 过度使用 .not 掩盖了测试意图的模糊性。

什么时候可以用 .not

有些场景「否定断言」是表达意图的最自然方式:

// 接口不应该报错
expect(error).to.not.exist;

// 集合不应该包含某个元素
expect(forbiddenItems).to.not.include('admin');

// 字符串不应该包含敏感词
expect(input).to.not.match(/password/);

// 对象不应该拥有某个属性(防止意外泄露)
expect(user).to.not.have.property('password');

这些例子里,断言"不应该有"比断言"应该没有 X 而有 Y"更清晰。

.not 与 .deep 的组合

修饰器可以自由组合:

// 深度不相等
expect({ a: 1 }).to.not.deep.equal({ a: 2 });

// 深度不包含某个嵌套对象
expect([{ a: 1 }]).to.not.deep.include({ a: 2 });

修饰器的顺序不影响结果:

expect({ a: 1 }).to.not.deep.equal({ a: 2 });
expect({ a: 1 }).to.deep.not.equal({ a: 2 });
// 两者等价

.not 与 .includeMembers 的搭配

includeMembers 断言 haystack 包含 needle 的所有成员(即 needle 是 haystack 的子集)。加 .not 后变成"haystack 不包含 needle 的所有成员":

expect([1, 2, 3]).to.not.includeMembers([4, 5]);  // 通过
expect([1, 2, 3]).to.not.includeMembers([1, 2]);  // 失败——haystack 包含 needle

这种场景下 .not 确实比反向写更清晰。

错误消息示例

正常断言失败:

expect(2).to.equal(3);
// AssertionError: expected 2 to equal 3

否定断言失败:

expect(2).to.not.equal(2);
// AssertionError: expected 2 to not equal 2

注意错误消息里会把 not 也拼进去,方便定位是哪个断言出错。

.does.not 这个组合

你可能看到 .does.not.have.property(...) 这种写法:

expect(obj).does.not.have.property('name');

这里的 .does 只是一个语言链(不做任何断言),.not 才是真正起作用的修饰器。去掉 .does 效果完全一样:

expect(obj).not.have.property('name');
expect(obj).to.not.have.property('name');  // 加 to 更自然

.does 的存在只是为了让 .not 在句子里读起来更顺。

实践建议

  1. 默认写正向断言。
  2. 遇到"不应该 X"的语义时才用 .not。
  3. 永远不要嵌套 .not.not。
  4. 结合其他修饰器(.deep)时,用最自然的顺序就行,不用刻意排列。

下一步