notiz-dev / nestjs-prisma

Easy Prisma support for your NestJS application
https://nestjs-prisma.dev
MIT License
587 stars 50 forks source link

CustomPrismaService unit testing #83

Open coryoso opened 11 months ago

coryoso commented 11 months ago

Hi! Thank you for nestjs-prisma, has been a great addition to our stack so far!

We're using the CustomPrismaService because we need to extend the client. Sadly the unit test setup does not work, i'm struggling with mocking the client. Right now it errors with TypeError: Cannot read properties of undefined (reading 'user') as the client is always undefined.

The userService looks like this:

@Injectable()
export class UsersService {
    constructor( 
        @Inject("PrismaService")
        private readonly prismaService: CustomPrismaService<ExtendedPrismaClient>, 
    ) {}

        findOne = async (id: string) => {
            return this.prismaService.client.user.findFirst({
                where: { id },
            })
        }
}

The userService.spec.test file like this:

describe("Usersservice", () => {
    let service: UsersService
    let prisma: DeepMockProxy<CustomPrismaService<ExtendedPrismaClient>>

    beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
            imports: [
                CustomPrismaModule.forRootAsync({
                    name: "PrismaService",
                    isGlobal: false,
                    useFactory: () => {
                        return extendedPrismaClient
                    },
                }),
            ],
            providers: [
                UsersService, 
                {
                    provide: PrismaService,
                    useValue: {},
                }, 
            ],
        })
            .overrideProvider(CustomPrismaService<ExtendedPrismaClient>)
            .useValue(mockDeep<PrismaClient>())
            .compile()

        service = module.get<UsersService>(UsersService)
        prisma = module.get<PrismaService>(PrismaService)
    })

        it("should find a user", async () => {
                const userID = "user-id"

                prisma.client.user.findFirst.mockResolvedValueOnce({
                    id: userID,
                    firstName: "John",
                    lastName: "Doe",
                    email: "johndoe@gmail.com",
                })

                expect(service.test(userID)).toEqual(userID)
        })
})

Appreciative of any help, thank you so much!!

nikola418 commented 9 months ago

Hi, I just started learning testing and using jest and I've encountered a similar problem and I think I've gotten a somewhat good grasp on the problem at hand.

So basically, when testing an NestJS that depends on another provider what you need to do is tell NestJS how to construct it (mock it). There are multiple approaches to achieving that but here's what I did: In your top-most beforeEach where you setup the NestJS testing module create a provider for the Provide Token you wish to mock, here I have an extended prisma client and I used mockDeep because that what Prisma docks suggest to use, I guess it gives better type-safety. Then retreive the mocked client so you can tell jest how you want to mock the return values of the prisma functions you call.

describe(CustomersService.name, () => {
  let customersService: CustomersService;
  let prismaMock: DeepMockProxy<ExtendedPrismaClient>;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [
        CustomersService,
        {
          provide: 'Prisma',
          useValue: <DeepMockProxy<CustomPrismaService<ExtendedPrismaClient>>>{
            client: mockDeep<ExtendedPrismaClient>(),
          },
        },
      ],
    }).compile();

    customersService = moduleRef.get(CustomersService);
    prismaMock =
      moduleRef.get<DeepMockProxy<CustomPrismaService<ExtendedPrismaClient>>>(
        'Prisma',
      ).client;
  });

Then in your describe block of the unit you're testing create a mock value. Here I did it by just constructing a dummy Prisma.Customer[] object and then told the prismaMock.customer.findMany to mock the resolved value to be that instance.


  describe('When findAll is called', () => {
    describe('and findMany returns an array of Customers', () => {
      let customers: Customer[];
      beforeEach(() => {
        customers = [
          {
            id: 1,
            address: '',
            addressLatitude: new Decimal(45),
            addressLongitude: new Decimal(45),
            currency: Currency.RSD,
            paymentMethod: PaymentMethod.OnDelivery,
            rating: new Decimal(0),
            ratingsCount: 0,
            createdAt: new Date(),
            updatedAt: new Date(),
          },
        ];
        mockReset(prismaMock);
        prismaMock.customer.findMany.mockResolvedValue(customers);
      });
      it(`Should return the array of Customers`, async () => {
        expect(await customersService.findAll()).toBe(customers);
      });
    });
  });

Again, I've just started learning testing and using jest, but I hope this helps!

nikola418 commented 9 months ago

If however you don't have an extended client or don't care about type-safety I encourage you to check out this blog: (https://wanago.io/2023/04/03/api-nestjs-unit-tests-prisma/)