How to unit test a transaction-wrapped function in Prisma?
Asked Answered
M

1

10

I'm very new to Prisma and NestJS. I have the following transaction-wrapped function that I want to unit test by mocking the internal function reserveSingleBook.

This is my code (it does a few actions in a for loop and I want all those actions to be successful before committing the transaction).

async reserveBooks(reserveBooksDto: ReserveBoksDto): Promise<SuccessfulReservationResponse> {
    return await this.prisma.$transaction(async (prisma) => {
    
        const reservedBooksResponse = new SuccessfulReservationResponse();
    
        for (const reservation of reserveBooksDto.reservations){
    
          const bookBucket = await this.reserveSingleBook(
            prisma,
            reserveBooksDto.libraryId,
            reservation,
          );
    
          const successfulReservation = new SuccessfulReservation();
          successfulReservation.book_bucket_id = bookBucket.id;
          successfulReservation.units_reserved = reservation.units;
    
          reservedBookssResponse.successful_reservations.push(successfulReservation);    
        }
    
        return reservedBooksResponse
    })
}

This is how I'm currently unit testing it:

it('should throw an exception when at least a parent book does not exist', async () => {
    // Arrange
    const nonExistingParentBookId = 'non-existing-parentbook-id';
    const existingParentBookId = 'existing-parent-book-id'; 
    const mock = jest.spyOn(service, 'reserveSingleBook');
    mock
    .mockResolvedValueOnce(
      {
        parentBookId: existingParentBookId,
        reserved: 10,
        used: 0,
      } as BookBucket
    )
    .mockRejectedValueOnce(new NotFoundException())
    const reserveBooksDto = new ReserveBooksDto();
    reserveBooksDto.library_id= 'abcde';
    const firstReservation = new Reservation();
    firstReservation.book_id= nonExistingParentBookId;
    firstReservation.units = 10;
    const secondReservation = new Reservation();
    secondReservation.book_id= existingParentBookId;
    secondReservation.units = 10;
    reserveBooksDto.reservations = [firstReservation, secondReservation];

    // Act / Assert
    await expect(service.reserveBooks(
      reserveBooksDto
    )).rejects.toThrowError(NotFoundException);
    expect(mock).toBeCalledTimes(2);

    mock.mockRestore();
});

The unit test works PERFECTLY if I remove the transaction, since my second mock call returns the NotFoundException, which is then passed on to ReserveBooks.

However, when I have the transaction in place (don't want anything commited if anything fails), I get an 'undefined' resolved in my function call, instead of the exception thrown.

Does anybody know what I might be doing wrong?

Thanks in advance!!

Marvel answered 16/7, 2022 at 19:49 Comment(0)
R
5

You have to spy on the $transaction function and mock its implementation, so that it uses mocked prisma instance as the callback argument.

I'm not sure how you are mocking prisma here, but something like this should do the trick:

jest.spyOn(prisma, '$transaction').mockImplementation((callback) => callback(prismaMock))

Where prismaMock may look like:

export const prismaMock = {
  entity: { findUnique: jest.fn() }
}

With prismaMock like that, you can actually avoid having to add a spy to each test:

export const prismaMock = {
  entity: { findUnique: jest.fn() },
  $transaction: jest.fn().mockImplementation((callback) => callback(prismaMock))
}

Hope that helps. In fact this approach can be applied to mocking any function with callbacks

Rationalism answered 19/5, 2023 at 6:6 Comment(2)
In my case, I actually needed to declare the entity in prismaMock as entity: {findUnique: jest.fn()}. Needed an object, not jest function returning an object.Anthozoan
You are correct, I will update the answerRationalism

© 2022 - 2024 — McMap. All rights reserved.