Jest
题目描述
实现一个简化版的 Jest 测试框架核心功能:
expect(value).toBe(expected)- 基本相等断言expect(value).not.toBe(expected)- 取反断言- 返回
true(通过)或false(失败)
js
expect(3).toBe(3) // ✅ true
expect(4).toBe(3) // ❌ false
expect(3).not.toBe(3) // ❌ false
expect(4).not.toBe(3) // ✅ true扩展思考: 如何避免硬编码,提高代码健壮性?
本题考查 测试框架设计、链式调用 和 getter 方法 的使用。
核心知识点
1. 测试框架基础
- 断言 (Assertion): 验证代码行为是否符合预期
- 测试用例: 包含输入、预期输出和实际输出的比较
- 匹配器 (Matcher): 如
toBe、toEqual、toContain等 - 修饰符: 如
not用于取反断言结果
2. Object.is() vs ===
- Object.is(): 更严格的相等比较
- 特殊情况:
NaN === NaN(false) vsObject.is(NaN, NaN)(true) - 0 和 -0:
0 === -0(true) vsObject.is(0, -0)(false) - Jest 选择: Jest 的
toBe内部使用Object.is
3. getter 方法的妙用
- 动态计算: 每次访问时执行逻辑
- 链式调用: 返回
this实现方法链 - 状态切换: 通过 getter 改变内部状态
代码实现
javascript
/**
* @param {any} input
* @returns {ExpectObject}
*/
export default function myExpect(input) {
let isReversed = false // 标记是否使用 not 修饰符
return {
toBe(checker) {
const isEqual = Object.is(input, checker)
// 根据是否使用 not 决定结果
// not + 相等 = false, not + 不相等 = true
// 普通 + 相等 = true, 普通 + 不相等 = false
if ((isReversed && isEqual) || (!isReversed && !isEqual)) {
return false
}
else {
return true
}
},
get not() {
isReversed = !isReversed // 切换取反状态
return this // 返回自身支持链式调用
},
}
}关键技术点
1. 逻辑判断的简化
javascript
// 原始复杂逻辑
if (isReversed) {
return !isEqual; // not 修饰符:取反结果
} else {
return isEqual; // 正常情况:直接返回
}
// 简化后的逻辑
if ((isReversed && isEqual) || (!isReversed && !isEqual)) {
return false;
} else {
return true;
}
// 进一步简化
return !((isReversed && isEqual) || (!isReversed && !isEqual));
// 德摩根定律
return (!isReversed || !isEqual) && (isReversed || isEqual);
// 最终简化
return isReversed ? !isEqual : isEqual;2. getter 的链式调用机制
javascript
get not() {
console.log('Accessing not getter')
isReversed = !isReversed
return this // 关键:返回 this 对象
}
// 使用示例
const expectObj = myExpect(3)
expectObj.not.not.not.toBe(4) // 可以多次链式调用3. Object.is() 的特殊情况
javascript
// 测试 Object.is 与 === 的差异
console.log(Number.NaN === Number.NaN) // false
console.log(Object.is(Number.NaN, Number.NaN)) // true
console.log(0 === -0) // true
console.log(Object.is(0, -0)) // false
console.log(+0 === -0) // true
console.log(Object.is(+0, -0)) // false
// 为什么 Jest 选择 Object.is?
// 1. 更符合数学直觉(NaN 等于自身)
// 2. 区分 +0 和 -0 的细微差别
// 3. 提供更严格的相等性检查4. 常见陷阱和坑点
- 状态污染:
not状态在多次调用间可能被污染 - 链式调用理解: getter 每次都会执行,修改状态
- 相等比较选择: 选择合适的比较方法很重要
- 返回值混淆: 断言结果应该返回布尔值
设计模式
javascript
// 建造者模式 + 链式调用
const result = myExpect(value)
.not // 修饰符
.toBe(expected) // 断言方法
// 状态机模式
// State: normal -> not -> normal -> not扩展思考
1. 避免硬编码的改进版本
javascript
function myExpect(input) {
const modifiers = { isReversed: false }
const expectObj = {
toBe(checker) {
const isEqual = Object.is(input, checker)
return modifiers.isReversed ? !isEqual : isEqual
},
}
// 动态添加 not getter
Object.defineProperty(expectObj, 'not', {
get() {
modifiers.isReversed = !modifiers.isReversed
return this
},
})
return expectObj
}2. 支持更多匹配器
javascript
function myExpectExtended(input) {
let isReversed = false
const matchers = {
toBe(expected) {
const result = Object.is(input, expected)
return isReversed ? !result : result
},
toEqual(expected) {
const result = deepEqual(input, expected)
return isReversed ? !result : result
},
toContain(item) {
const result = Array.isArray(input) && input.includes(item)
return isReversed ? !result : result
},
toBeGreaterThan(num) {
const result = typeof input === 'number' && input > num
return isReversed ? !result : result
},
toThrow() {
let threw = false
try {
if (typeof input === 'function')
input()
}
catch (e) {
threw = true
}
return isReversed ? !threw : threw
},
}
Object.defineProperty(matchers, 'not', {
get() {
isReversed = !isReversed
return this
},
})
return matchers
}
// 深度相等比较辅助函数
function deepEqual(a, b) {
if (a === b)
return true
if (a == null || b == null)
return false
if (typeof a !== typeof b)
return false
if (typeof a === 'object') {
const keysA = Object.keys(a)
const keysB = Object.keys(b)
if (keysA.length !== keysB.length)
return false
for (const key of keysA) {
if (!keysB.includes(key))
return false
if (!deepEqual(a[key], b[key]))
return false
}
return true
}
return false
}3. 支持异步断言
javascript
function myExpectAsync(input) {
let isReversed = false
return {
async toResolve() {
try {
await input
return !isReversed
}
catch (e) {
return isReversed
}
},
async toReject() {
try {
await input
return isReversed
}
catch (e) {
return !isReversed
}
},
get not() {
isReversed = !isReversed
return this
},
}
}
// 使用示例
const promise1 = Promise.resolve(42)
const promise2 = Promise.reject('error')
console.log(await myExpectAsync(promise1).toResolve()) // true
console.log(await myExpectAsync(promise2).not.toResolve()) // true