Chai 中文文档 下载 App

对象属性断言完整参考 - property keys include empty deep property

完整断言方法一览

断言语义示例
.property(key)拥有某个属性(值存在就行)expect(obj).to.have.property('name')
.property(key, value)拥有属性且值精确匹配expect(obj).to.have.property('name', 'Alice')
.deep.property(path, value)嵌套路径 + 深度值expect(obj).to.have.deep.property('user.name', 'Alice')
.deep.property(path)嵌套路径存在expect(obj).to.have.deep.property('user.name')
.all.keys(k1, k2)精确键集合匹配expect(obj).to.have.all.keys('name', 'age')
.any.keys(k1, k2)包含任意给定键expect(obj).to.have.any.keys('name', 'phone')
.include({...})对象包含给定属性(部分匹配)expect(obj).to.include({ name: 'Alice' })
.deep.include({...})深度部分匹配嵌套对象用这个
.empty空对象(没有自有键)expect({}).to.be.empty
.not.property(key)不拥有某属性expect(user).to.not.have.property('password')

.property(key) —— 属性存在性

const obj = { name: 'Alice', age: 30 };

expect(obj).to.have.property('name');   // ✅ 存在就行
expect(obj).to.have.property('email');  // ❌ 不存在

.property(key, value) —— 属性值精确匹配

expect(obj).to.have.property('name', 'Alice');  // ✅ 存在且值精确匹配
expect(obj).to.have.property('age', 25);        // ❌ 值不对

深度值匹配

属性值是对象时,默认还是严格相等。加 .deep 或 .property(key, value) 值为深度相等:

const obj = { meta: { version: '1.0' } };

// ❌ 属性值是对象,默认严格相等
expect(obj).to.have.property('meta', { version: '1.0' });

// ✅ 用 .deep.property
expect(obj).to.have.deep.property('meta', { version: '1.0' });

.deep.property(path, value) —— 嵌套路径

用点号分隔的路径访问嵌套属性:

const user = { profile: { name: 'Alice', settings: { theme: 'dark' } } };

// 深度嵌套路径
expect(user).to.have.deep.property('profile.settings.theme', 'dark');
expect(user).to.have.deep.property('profile.name');

// 路径上任何一层不存在都会抛错
expect(user).to.have.deep.property('profile.contact.email');  // ❌ profile.contact 不存在

数组下标

路径也可以包含数组下标(点号分隔):

const data = { items: [{ name: 'first' }, { name: 'second' }] };
expect(data).to.have.deep.property('items.0.name', 'first');

注意:路径是点号分隔不是 JS 方括号。items[0].name 不会正确解析,要用 items.0.name。

.keys —— 键集合检查

.all.keys:精确键集合

const obj = { name: 'Alice', age: 30, email: 'a@b.com' };

expect(obj).to.have.all.keys('name', 'age', 'email');       // ✅ 完全匹配
expect(obj).to.have.all.keys('name', 'age');               // ❌ 少了 email
expect(obj).to.have.all.keys('name', 'age', 'email', 'phone');  // ❌ 多了 phone

键的顺序无所谓,是集合比较。

.any.keys:包含任意键

// 只要有其中一个就行
expect(obj).to.have.any.keys('phone', 'fax', 'name');  // ✅ name 存在
expect(obj).to.have.any.keys('phone', 'fax');          // ❌ 都不存在

对象和数组都可以传

expect(obj).to.have.all.keys('name', 'age');            // 多个独立参数
expect(obj).to.have.all.keys(['name', 'age']);          // 也可以传数组

Set / Map 也支持

const set = new Set(['a', 'b']);
expect(set).to.have.all.keys(['a', 'b']);
expect(set).to.have.any.keys(['a', 'c']);

.include —— 对象部分匹配

.include 对对象的语义是:对象包含给定的所有属性(多出来没关系):

const obj = { name: 'Alice', age: 30, email: 'a@b.com' };

// ✅ obj 包含 { name: 'Alice' }
expect(obj).to.include({ name: 'Alice' });

// ❌ obj 里没有 phone 属性
expect(obj).to.include({ name: 'Alice', phone: '123' });

加 .deep 支持嵌套对象的部分匹配:

const obj = { profile: { name: 'Alice', settings: { theme: 'dark', fontSize: 14 } } };

expect(obj).to.deep.include({
  profile: {
    settings: { theme: 'dark' }  // 只关心 theme,fontSize 无所谓
  }
});  // ✅

.empty —— 空对象

expect({}).to.be.empty;             // ✅ 没有自有键
expect({ a: 1 }).to.not.be.empty;   // ✅

注意 .empty 是检查自有可枚举属性,原型链上的属性不算。用 Object.create(Object.prototype) 创建的对象如果自身没有属性,也算空。

但 Object.create(null) 创建的对象(没有原型链),只要自身没有属性也算空。

.not.property —— 不拥有某属性

// 防止敏感字段泄露
expect(user).to.not.have.property('password');
expect(response.body).to.not.have.property('internalToken');

这个场景用 .not.property 比 .not.include 更精确——你是在验证这个字段不存在,而不是验证字段值不等于某个值。

assert 风格对应

assert.isObject(value);
assert.isPlainObject(value);        // 只检查普通对象(非 Date/RegExp 等)
assert.property(obj, key);
assert.propertyVal(obj, key, val);
assert.notProperty(obj, key);
assert.deepProperty(obj, path);
assert.deepPropertyVal(obj, path, val);
assert.hasAllKeys(obj, keys);
assert.hasAnyKeys(obj, keys);
assert.doesNotHaveAllKeys(obj, keys);

常见陷阱

.property(key, value) 忘记加 .deep

属性值是对象时:

// ❌ 严格相等——永远匹配不上
expect(obj).to.have.property('meta', { version: '1.0' });

// ✅ 要么加 .deep
expect(obj).to.have.deep.property('meta', { version: '1.0' });

// ✅ 要么用 .deep.include
expect(obj).to.deep.include({ meta: { version: '1.0' } });

.all.keys 和 .includeMembers 混淆

大多数测试场景 .include 更合适——API 返回的对象可能不断增加新字段,你只关心已有的关键字段。

原型链属性被 .keys 误判

.keys / .all.keys 只检查自有属性(Object.hasOwn),不遍历原型链:

function Foo() { this.name = 'test'; }
Foo.prototype.legacy = 'old';
const foo = new Foo();

expect(foo).to.have.all.keys('name');  // ✅ 只检查自有属性
expect(foo).to.have.property('legacy'); // ✅ .property 会检查原型链

下一步