对象属性断言完整参考 - 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 混淆
.all.keys= 对象恰好拥有这些键(多一个少一个都不行).include({...})= 对象至少包含这些属性(多出来没关系)
大多数测试场景 .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 会检查原型链