如何在Jest的.toMatchObject中定义可选参数?
如何让Jest支持可选字段的对象结构校验?
我明白你想实现类似TypeScript接口那样的结构校验——必填字段严格校验,可选字段存在时校验类型、不存在时也不报错。你遇到的问题本质是Jest的toMatchObject会强制要求匹配对象中的所有键都存在于目标对象中,所以当目标对象没有Children字段时,它直接判定不匹配,根本不会触发你自定义的toBeUnnessary匹配器。
下面给你两种可行的解决方案:
方案一:自定义支持可选字段的结构匹配器
这是最贴合你需求的方案,我们可以自己实现一个toMatchPartialObject匹配器,支持标记可选字段,逻辑上完全模拟TS的接口校验:
1. 实现辅助函数和自定义匹配器
// 辅助函数:标记字段为可选 const optional = (matcher) => ({ isOptional: true, matcher: matcher }); // 扩展Jest匹配器 expect.extend({ toMatchPartialObject(received, expected) { const errorMessages = []; // 遍历预期结构的每个字段 for (const [key, expectedRule] of Object.entries(expected)) { const receivedValue = received[key]; // 处理可选字段 if (expectedRule.isOptional) { // 目标对象没有该字段,直接跳过 if (!(key in received)) continue; // 有该字段则用指定匹配器校验 const matchPassed = expectedRule.matcher.asymmetricMatch(receivedValue); if (!matchPassed) { errorMessages.push(`字段 ${key} 需匹配 ${expectedRule.matcher.toString()},但实际值为 ${receivedValue}`); } } else { // 处理必填字段 if (!(key in received)) { errorMessages.push(`缺少必填字段 ${key}`); continue; } // 兼容Jest内置匹配器(如expect.any(Number)) if (typeof expectedRule.asymmetricMatch === 'function') { const matchPassed = expectedRule.asymmetricMatch(receivedValue); if (!matchPassed) { errorMessages.push(`字段 ${key} 需匹配 ${expectedRule.toString()},但实际值为 ${receivedValue}`); } } else { // 普通值严格匹配 if (receivedValue !== expectedRule) { errorMessages.push(`字段 ${key} 应为 ${expectedRule},但实际值为 ${receivedValue}`); } } } } return { pass: errorMessages.length === 0, message: () => errorMessages.length ? `对象结构不匹配:\n${errorMessages.join('\n')}` : `对象结构匹配成功` }; } });
2. 使用方式
// 定义结构规则:Children标记为可选数组 const categoryObj = { CategoryID: expect.any(Number), Index: expect.any(Number), Name: expect.any(String), Children: optional(expect.any(Array)) }; // 用自定义匹配器校验 expect(category).toMatchPartialObject(categoryObj);
方案二:拆分校验逻辑(快速临时方案)
如果你不想写复杂的自定义匹配器,可以拆分校验步骤:先校验必填字段,再单独处理可选字段:
// 先校验必填字段的结构 expect(category).toMatchObject({ CategoryID: expect.any(Number), Index: expect.any(Number), Name: expect.any(String) }); // 单独处理可选字段:存在则校验类型,不存在则跳过 if ('Children' in category) { expect(category.Children).toBeInstanceOf(Array); // 或者用你自定义的toBeUnnessary:expect(category.Children).toBeUnnessary(Array); }
这个方案更简单,但字段多的时候会比较繁琐,适合小范围场景。
为什么原来的方法不生效?
再补充下原因帮你理解:toMatchObject的核心逻辑是目标对象必须包含匹配对象的所有键,它会先检查键的存在性,只有键存在时才会去校验对应的值。所以当你的目标对象没有Children时,toMatchObject直接抛出"缺少键"的错误,根本不会执行你给Children指定的toBeUnnessary匹配器。
内容的提问来源于stack exchange,提问作者吴晨凯
相关产品推荐
相关产品推荐

