如何为Yup Schema编写单元测试:实现单属性规则验证且无需构造完整有效对象
嘿,我完全懂你的痛点——面对20多个属性的Schema,每次测试都要凑出一个完整的有效对象,只改一个字段来验证规则,这实在太折腾了!而且之前用schema.validate()会触发所有字段的校验,导致无关错误干扰测试,完全不是我们想要的单元测试方式。
其实Yup早就提供了专门解决这个问题的方法:validateAt(异步)或者validateAtSync(同步)。这个方法允许你只针对单个字段进行验证,完全不用管其他字段的状态,完美匹配你想要的单属性规则测试需求。
核心解决思路
validateAt的用法非常直观:它接受两个参数,第一个是你要测试的字段名(比如'firstName'),第二个是包含该字段的对象(甚至可以只传这个字段,其他字段都不用填)。它只会验证指定的字段,不会触发其他字段的校验规则,彻底解决了构造完整对象的麻烦。
需要注意的是,Yup的验证默认是异步的,所以测试里最好用async/await来处理,或者用Vitest的.rejects断言简化代码。
针对你的示例写具体测试代码
先补全你的Schema定义(记得导入yup):
import * as yup from 'yup'; const schema = yup.object().shape({ firstName: yup.string().required().max(10), age: yup.number().required() });
接下来是两种常用的测试写法,你可以选自己习惯的:
方式1:用.rejects断言简化测试
这种写法最简洁,直接断言验证会抛出符合预期的错误:
import { describe, it, expect } from 'vitest'; import * as yup from 'yup'; describe('First name validation rules', () => { it('cannot be more than 10 characters', async () => { // 只传要测试的firstName,其他字段完全不用管 await expect(schema.validateAt('firstName', { firstName: '01234567891' })) .rejects.toThrow(/firstName must be at most 10 characters/); }); it('is required', async () => { await expect(schema.validateAt('firstName', { firstName: '' })) .rejects.toThrow('firstName is a required field'); }); }); describe('Age validation rules', () => { it('is required', async () => { // 甚至可以传空对象,测试age的必填规则 await expect(schema.validateAt('age', {})) .rejects.toThrow('age is a required field'); }); });
方式2:捕获错误后精确断言
如果需要更细致的验证(比如确认错误来自哪个字段、消息是否完全匹配),可以用try/catch捕获Yup.ValidationError:
import { describe, it, expect } from 'vitest'; import * as yup from 'yup'; describe('First name validation rules', () => { it('cannot be more than 10 characters', async () => { try { await schema.validateAt('firstName', { firstName: '01234567891' }); // 如果没抛出错误,就标记测试失败 expect.fail('Expected validation to fail for long first name'); } catch (error) { // 断言错误是Yup的ValidationError类型 expect(error).toBeInstanceOf(yup.ValidationError); // 断言错误来自firstName字段 expect(error.path).toBe('firstName'); // 断言错误消息符合预期 expect(error.message).toBe('firstName must be at most 10 characters'); } }); });
同步验证的写法
如果你偏好同步逻辑,也可以用validateAtSync方法,用法和validateAt完全一致,只是它会同步抛出错误,不需要await:
it('cannot be more than 10 characters', () => { expect(() => schema.validateAtSync('firstName', { firstName: '01234567891' })) .toThrow(/firstName must be at most 10 characters/); });
总结一下
- 用
validateAt/validateAtSync替代validate,实现单字段独立验证,不用再构造完整对象。 - 用异步测试的
async/await或者.rejects断言处理异步验证逻辑。 - 可以通过断言错误类型、字段路径或错误消息,确保你的验证规则完全符合预期。
这样你就能轻松为每个字段的每条规则编写独立的单元测试,再也不用为填充无关字段而头疼了!
备注:内容来源于stack exchange,提问作者Greg Burghardt




