NestJs - How to unit test a DTO?

Viewed 4845

I am asking you for help. I have created a DTO that looks like it (this is a smaller version) :

export class OfImportDto {

    @IsString({
        message: "should be a valid product code"
    })
    productCode: string;

    @IsString({
        message: "Enter the proper product description"
    })
    productDescription: string;

    @IsDateString({
        message: "should be a valid date format, for example : 2017-06-07T14:34:08+04:00"
    })
    manufacturingDate : Date

    @IsInt({
        message: "should be a valid planned quantity number"
    })
    @IsPositive()
    plannedQuantity: number;

the thing is that i am asking to test that, with a unit test and not a E2E test. And I Don't know how to do that. For instance, I would like to unit test 1/ if my product code is well a string, a string should be created, if not, throw my exception 2/ if my product description is well a string, a string should be created, if not, throw my exception ... and so on.

So, can I made a spec.ts file to test that? If yes, how? If not, is it better to test it within the service.spec.ts? If so, how?

Thank you very much, any help would be very helpful :)

2 Answers

It would be possible to create a OfImportDTO.spec.ts file (or whatever your original file is called), but the thing is, there isn't any logic here to test. The closest thing you could do is create an instance of a Validator from class-validator and then instantiate an instance of the OfImportDto and then check that the class passes validation. If you add logic to it (e.g. getters and setters with specific functions) then it could make sense for unit testing, but otherwise, this is basically an interface being called a class so it exists at runtime for class-validator

You should create a separate DTO specific file like of-import.dto.spec.ts for unit tests of your DTOs. Let's see how to test a DTO in Nest.js step by step.


TLDR: Your DTO's unit test

To understand it line by line, continue reading:

it('should throw when the planned quantity is a negative number.', async () => {
  const importInfo = { productCode: 4567, plannedQuanity: -10 }
  const ofImportDto = plainToInstance(OfImportDto, importInfo)
  const errors = await validate(ofImportDto)
  expect(errors.length).not.toBe(0)
  expect(stringified(errors)).toContain(`Planned Quantity must be a positive number.`)
}

Create a plain object to test

The following is an object that you would like to test for validation:

const importInfo = { productCode: 4567, plannedQuanity: -10 }

If your test object has nested objects or it's too big, you can skip those complex properties. We'll see how to deal with that.


Convert the test object to the type of DTO

Use plainToinstace() function from the class-transformer package:

const ofImportDto = plainToInstance(OfImportDto, importInfo)

This will turn your plain test object to the object of the type of your DTO, that is, OfImportDto.

If you have any transformations in your DTO, like trimming spaces of the values of properties, they are applied at this point. If you just intend to test the transformations, you can assert now, you don't have to call the following validate() function for testing transformations.


Emulate the validation

Use the validate() function from the class-validator package:

const errors = await validate(ofImportDto)

If you skipped some properties while creating the test object, you can deal with that like the following:

const errors = await validate(ofImportDto, { skipMissingProperties: true })

Now the validation will ignore the missing properties.


Assert the errors

Assert that the errors array is not empty and it contains your custom error message:

expect(errors.length).not.toBe(0)
expect(stringified(errors)).toContain(`Planned Quantity must be a positive number.`)

Here stringified() is a helper function to convert the errors object to a JSON string, so we can search if it contains our custom error message:

export function stringified(errors: ValidationError[]): string {
  return JSON.stringify(errors)
}

That's it! Hope that helps.

Related