Bob*_*Bob 2 testing validation dto nestjs
TLNR:我试图在控制器规范中而不是在 e2e 规范中测试 DTO 验证,后者正是为此而设计的。McDoniel 的回答为我指明了正确的方向。
我开发了一个 NestJS 入口点,如下所示:
@Post()
async doStuff(@Body() dto: MyDto): Promise<string> {
// some code...
}
Run Code Online (Sandbox Code Playgroud)
我使用class-validator这样当我的 API 收到请求时,有效负载被解析并转换为 MyDto 对象,并执行作为 MyDto 类中的注释的验证。请注意,MyDto 有一个 MySubDto 类的嵌套对象数组。使用@ValidateNested 和@Type 注释,嵌套对象也可以正确验证。
这很好用。
现在我想为执行的验证编写测试。在我的 .spec 文件中,我写道:
import { validate } from 'class-validator';
// ...
it('should FAIL on invalid DTO', async () => {
const dto = {
//...
};
const errors = await validate( dto );
expect(errors.length).not.toBe(0);
}
Run Code Online (Sandbox Code Playgroud)
这将失败,因为经过验证的 dto 对象不是 MyDto。我可以这样重写测试:
it('should FAIL on invalid DTO', async () => {
const dto = new MyDto()
dto.attribute1 = 1;
dto.subDto = { 'name':'Vincent' };
const errors = await validate( dto );
expect(errors.length).not.toBe(0);
}
Run Code Online (Sandbox Code Playgroud)
现在可以在 MyDto 对象上正确进行验证,但不能在我嵌套的 subDto 对象上进行验证,这意味着我必须使用相应的类实例化 Dto 的所有对象,这将非常低效。此外,实例化类意味着如果我自愿省略一些必需的属性或指示不正确的值,TypeScript 将引发错误。
所以问题是:
如何在我的测试中使用 NestJs 内置请求正文解析器,以便我可以为 dto 编写任何我想要的 JSON,将其解析为MyDto 对象并使用class-validator验证函数对其进行验证?
也欢迎任何其他更好的方法来测试验证!
Yog*_*ity 35
尽管如此,我们应该测试我们的验证 DTO 如何与 一起工作ValidationPipe,这是集成或 e2e 测试的一种形式。单元测试就是单元测试,对吧?!每个单元都应该是可独立测试的。
Nest.js 中的 DTO 是完美的单元可品尝的。当 DTO 包含复杂的正则表达式或卫生逻辑时,有必要对 DTO 进行单元测试。
您正在寻找的 Nest.js 中的请求正文解析器就是该class-transformer包。plainToInstance()它具有将文本或 JSON 对象转换为指定类型的对象的功能。在您的示例中,指定的类型是 DTO 的类型:
const myDtoObject = plainToInstance(MyDto, myBodyObject)
Run Code Online (Sandbox Code Playgroud)
这里myBodyObject是您为测试创建的普通对象,例如:
const myBodyObject = { attribute1: 1, subDto: { name: 'Vincent' } }
Run Code Online (Sandbox Code Playgroud)
该plainToInstance()函数还应用 DTO 中的所有转换。如果您只想测试转换,可以在此语句后断言。您不必调用该validate()函数来测试转换。
要模拟 Nest.js 的验证,只需将 传递myDtoObject给包validate()的函数class-validator:
const errors = await validate(myDtoObject)
Run Code Online (Sandbox Code Playgroud)
此外,如果您的 DTO 或 SubDTO 对象太大或太复杂而无法创建,您可以选择跳过其余属性或子对象,例如subDto:
const errors = await validate(myDtoObject, { skipMissingProperties: true })
Run Code Online (Sandbox Code Playgroud)
现在您的测试对象可能没有subDto,例如:
const myBodyObject = { attribute1: 1 }
Run Code Online (Sandbox Code Playgroud)
除了断言数组errors不为空之外,我还喜欢为 DTO 中的每个验证指定自定义错误消息:
@IsPositive({ message: `Attribute1 must be a positive number.` })
readonly attribute1: number
Run Code Online (Sandbox Code Playgroud)
自定义错误消息的优点之一是我们可以以用户友好的方式编写它,而不是由库创建的通用消息。另一个很大的优点是我可以在测试中断言此错误消息。这样我就可以确保数组errors不为空,因为它包含此特定验证的错误而不是其他内容:
expect(stringified(errors)).toContain(`Attribute1 must be a positive number.`)
Run Code Online (Sandbox Code Playgroud)
这里stringified()是一个简单的实用函数,用于将错误对象转换为 JSON 字符串,以便我们可以在其中搜索错误消息:
export function stringified(errors: ValidationError[]): string {
return JSON.stringify(errors)
}
Run Code Online (Sandbox Code Playgroud)
controller.spec.ts创建一个特定于您的 DTO 的新文件,而不是该文件,例如my-dto.spec.tsDTO 的单元测试。DTO 可以有大量的单元测试,并且它们不应与控制器的测试混合:
it('should fail on invalid DTO', async () => {
const myBodyObject = { attribute1: -1, subDto: { name: 'Vincent' } }
const myDtoObject = plainToInstance(MyDto, myBodyObject)
const errors = await validate(myDtoObject)
expect(errors.length).not.toBe(0)
expect(stringified(errors)).toContain(`Attribute1 must be a positive number.`)
}
Run Code Online (Sandbox Code Playgroud)
请注意,您不必将值一一分配给属性来创建myDtoObject. 在大多数情况下,DTO 的属性应标记为readonly。所以,你不能一一分配这些值。plainToInstance()来救援吧!
就是这样!您即将完成 DTO 的单元测试。好努力!希望现在有帮助。
为了使用验证管道测试输入验证,我认为最好的地方是在 e2e 测试中而不是在单元测试中,只需确保您记得注册您的管道(如果您通常使用app.useGlobalPipes()而不是使用依赖项)注射)
| 归档时间: |
|
| 查看次数: |
4541 次 |
| 最近记录: |