typestack / class-transformer

Decorator-based transformation, serialization, and deserialization between objects and classes.
MIT License
6.82k stars 500 forks source link

question: How to test @Type() decorator in Jest #874

Open jjteoh-pingspace opened 3 years ago

jjteoh-pingspace commented 3 years ago

I was trying to write test with Jest, my coverage report complain that the two lines with @Type() decorator were not covered.

I am looking for the right way(best practice) to write test for those two lines.

export abstract class AbstractCustomerModel {
  id: number
  date_created: Date
  date_modified: Date
  email: string
  first_name: string
  last_name: string
  role: string
  username: string
  password: string
  avatar_url: string
  phone: string

  @Type(() => ShippingProp)  // not covered
  shipping: ShippingProp

  @Type(() => BillingProp) // not covered
  billing: BillingProp
}
NicholasKuchiniski commented 3 years ago

Maybe this isn't your scenario, but I had a similar problem in a monorepo and I'm sharing the solution I found:

Check that your class's return is with the object in question as an instance of the class you expect in @Type. I got to this issue because I had the same problem with my tests and found some symptoms:

I don't know how to explain the problem, but I solved it by passing the function that called plainToClass, as a parameter in the class method that called it directly before.

Example:

packages/libs/my-response.ts

export type DataParser<T> = (data: any) => T;

export class MyResponse {
 public constructor(private readonly data: unknown) {}

  public getData<T>(parse: DataParser<T>): Promise<T> {
    const data = parse(this.data);

    return data;
  }

}
packages/other-lib/use-my-response.ts

import { plainToClass } from "class-transformer";
import { MyResponse } from "@mypackage/libs/my-response";

const parser = (data: any) => plainToClass(MyClass, data);

// response is now covered in the `@Type()` decorator
const response = new MyResponse(data).getData(parser);
cameronroudebush commented 2 years ago

I came across this same issue while trying to implement this package into one of my projects. I am not going to say this is the "best practice" way to fix it, but it did hit my coverage.

jest.mock("class-transformer", () => {
    return {
        ...(jest.requireActual("class-transformer") as Object),
        Type: (typeReturn: Function) => {
            // Call the internal type return function to satisfy coverage
            typeReturn();
            // Call the actual type request and return it to still allow the metadata to be set correctly
            return jest.requireActual("class-transformer").Type(typeReturn);
        },
    };
});

I placed that mock in my setup file (setupFilesAfterEnv) for jest.

akojiezekiel commented 1 year ago

Hoping this would be useful to anyone facing similar issue. I used the idea from this thread to come up with a solution:

// dto.ts
export class MyDto {

  @IsNotEmpty()
  @Type(() => Boolean)
  myBooleanFlag: boolean;    
...
}

In your spec, create a plain object and convert it to your Dto type using plainToInstance which you can import from the class-transformer package (node module):

import { plainToInstance } from 'class-transformer';
...
const obj = {
    myBooleanFlag = 'true',
}

const dtoInstance = plainToInstance (MyDto, obj)

By specifying plain values on the object, the @Type decorator would do the required transformation and get picked up in your coverage.

mtharrison commented 6 months ago

Using Typescript 5 and setting experimentalDecorators: false, emitDecoratorMetadata: false in my tsconfig.json solve my coverage problems.