rinogo
rinogo

Reputation: 9163

Testing a NestJS Service that uses Prisma without actually accessing the database

Most examples I've seen of how to test a Prisma-injected NestJS Service (e.g. prisma-sample in testing-nestjs) are for "end to end" testing. They actually access the database, performing actual queries and then rolling back the results if necessary.

For my current needs, I want to implement lower-level "integration" testing.

As part of this, I want to remove Prisma from the equation. I want the focus to be on my service's functionality instead of the state of data within the database and Prisma's ability to return it.

One big win of this approach is that it obviates the need to craft "setup" queries and "teardown"/reset operations for specific tests. Instead, I'd like to simply manually specify what we would expect Prisma to return.

In an environment consisting of NestJS, Prisma, and Jest, how should I accomplish this?


UPDATE: The author of the testing-nestjs project pointed out in the comments that the project does have an example of database mocking. It looks nice! Others may still be interested in checking out the Gist that I've linked to as it includes some other useful functionality.

Upvotes: 17

Views: 20856

Answers (3)

David Ferreira
David Ferreira

Reputation: 1766

You can also mock the whole Prisma client by using the jest-mock-extended package, which is the approach used in the official Prisma documentation.

Assuming you have created a PrismaService as suggested in the NestJS docs, the test would be:

import { Test, TestingModule } from '@nestjs/testing'
import { PrismaClient } from '@prisma/client'
import { mockDeep, DeepMockProxy } from 'jest-mock-extended'
    
describe('UserService', () => {
  let service: UserService;
  let prisma: DeepMockProxy<PrismaClient>;
    
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [UserService, PrismaService],
    })
      .overrideProvider(PrismaService)
      .useValue(mockDeep<PrismaClient>())
      .compile();
    
    service = module.get(UserService);
    prisma = module.get(PrismaService);
  });
    

  it('returns users', () => {
    const testUsers = [];

    prisma.user.findMany.mockResolvedValueOnce(testUsers);

    expect(service.findAll()).resolves.toBe(testUsers);
  });
});

One benefit of this approach is that you can easily mock on the Prisma client using the Jest Mock functions API.

Upvotes: 15

Ilya Kushlianski
Ilya Kushlianski

Reputation: 968

Nest.js offers an API for mocking whole services, and Prisma is just one of them.

For simplicity, let's assume we access prisma directly from app controller.

import { Controller, Get } from '@nestjs/common';
import { DbService } from 'src/db/db.service';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService,
    private readonly prisma: DbService,
  ) {}

  @Get()
  async getHello(): Promise<string> {
    const result = await this.prisma.user.findMany();

    console.log('result', result);

    return this.appService.getHello();
  }
}

Then the test would be:

describe('AppController', () => {
  let appController: AppController;

  const mockPrisma = {
    user: { findMany: () => Promise.resolve([]) },
  };

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      controllers: [AppController],
      providers: [AppService, DbService],
    })
      .overrideProvider(DbService)
      .useValue(mockPrisma)
      .compile();

    appController = app.get<AppController>(AppController);
  });

  describe('root', () => {
    it('should return "Hello World!"', () => {
      expect(appController.getHello()).resolves.toBe('Hello World!');
    });
  });
});

DbService is the following file (as suggested here https://docs.nestjs.com/recipes/prisma#use-prisma-client-in-your-nestjs-services):

@Injectable()
export class DbService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close();
    });
  }
}

Be sure to use the instance of DbService, not string 'DbService'. Otherwise a DB call is still made and the test fails.

Upvotes: 6

rinogo
rinogo

Reputation: 9163

To get a reference to your service's prisma instance, use:

prisma = module.get<PrismaService>(PrismaService)

Then, assuming your function calls prisma.name.findMany(), you can use jest.fn().mockReturnValueOnce() to mock (manually specify) Prisma's next return value:

prisma.name.findMany = jest.fn().mockReturnValueOnce([
    { id: 0, name: 'developer' },
    { id: 10, name: 'architect' },
    { id: 13, name: 'dog walker' }
]);

(Of course, you would change prisma.name.findMany in the code above to match whatever function you're calling.)

Then, call the function on your Service that you're testing. For example:

expect(await service.getFirstJob("steve")).toBe('developer');

That's it! A full code example can be found here.

Upvotes: 20

Related Questions