如何对编译器可强制执行的Post标题必填需求做单元测试?
这是个非常务实的问题——虽然TypeScript的严格空检查在编译阶段帮我们拦住了不少错误,但单元测试依然是守住业务规则的关键防线,咱们一步步说清楚:
一、怎么写单元测试验证这个需求?
因为TS严格模式下直接传null/undefined/空字符串会触发编译报错,所以我们需要绕过类型检查,模拟运行时可能出现的非法输入,验证构造函数的报错逻辑。以Jest为例,测试代码可以这么写:
import { Post } from './Post'; describe('Post Domain Object', () => { // 测试空字符串场景 test('throws error when title is an empty string', () => { // 用类型断言绕过TS编译检查,模拟运行时传入空值 expect(() => new Post('' as string)).toThrow('Post title should not be empty'); }); // 测试null场景 test('throws error when title is null', () => { // 用@ts-expect-error告诉TS:我们预期这里会有编译错误,不用提示 // @ts-expect-error expect(() => new Post(null)).toThrow('Post title should not be empty'); }); // 测试undefined场景 test('throws error when title is undefined', () => { // @ts-expect-error expect(() => new Post(undefined)).toThrow('Post title should not be empty'); }); // 正向测试:合法标题能创建Post test('creates Post successfully with valid title', () => { const post = new Post('My First DDD Post'); expect(post).toBeInstanceOf(Post); // 如果给title加了getter方法,还可以验证值:expect(post.title).toBe('My First DDD Post'); }); });
这么做的原因是:虽然编译时TS会拦截非法输入,但运行时依然有可能出现这种情况(比如前端传过来的JSON缺少title字段被解析成undefined,或者有人用纯JS调用这个类),测试运行时的校验逻辑才能确保业务规则真正生效。
二、能不能忽略这个测试?绝对不行!
现在TS严格模式帮我们挡了一部分问题,但未来可能出现这些场景:
- 有人修改
tsconfig.json把strict改成false,编译时的空检查就失效了; - 有人重构Post类,不小心把构造函数参数改成可选(比如
constructor(title?: string)); - 有人调整校验逻辑,比如把
!this.title改成this.title === null,漏掉了空字符串的情况。
单元测试是业务规则的可执行文档,它能在代码被修改后立刻提醒开发者:你破坏了“Post标题必填”的核心规则,避免无意识的回归错误。
三、如果未来有人把title设为可选怎么办?
这正是单元测试的价值所在——当有人修改代码违反业务规则时,对应的测试用例会直接失败,让开发者立刻意识到问题。比如如果有人把构造函数改成constructor(title?: string),测试new Post(undefined)的用例就会抛出错误,提醒开发者:这个改动不符合需求,必须回滚或者重新评估。
额外优化:类型系统+运行时校验双重保障
如果想让类型规则和业务规则绑定得更紧密,可以自定义一个非空字符串类型,从类型层面强制约束输入:
// 定义一个标记类型,确保只有经过校验的字符串才能传入 type NonEmptyString = string & { __brand: 'NonEmptyString' }; // 辅助函数:验证字符串非空,返回标记类型 function ensureNonEmptyString(value: string): NonEmptyString { if (!value.trim()) { throw new Error('String should not be empty or whitespace'); } return value as NonEmptyString; } export class Post { private title: NonEmptyString; constructor(title: NonEmptyString) { this.title = title; } } // 使用时必须先通过辅助函数校验 const validPost = new Post(ensureNonEmptyString('Valid Title')); // new Post('') 会直接编译报错,因为''不是NonEmptyString类型
但即便做了这样的类型优化,单元测试依然不能少——辅助函数的校验逻辑、Post类的构造函数逻辑,都需要测试来确保正确性。
内容的提问来源于stack exchange,提问作者Mohamed Ramrami
相关产品推荐
相关产品推荐

