Typeorm Unit Test FindOneOrFail

Viewed 37

I am trying to add unit tests for my Nest app that uses typeorm. My user service has a mock repository set up like this:

const mockUsersRepository = {
create: jest.fn().mockImplementation((dto) => dto),
insert: jest.fn((user) => Promise.resolve({ id: 1, ...user })),
findOneOrFail: jest.fn(({ where: { id } }) =>
  Promise.resolve({
    id: id,
    firstName: 'John',
    lastName: 'Smith',
    created: '2022-08-18T04:43:26.035Z',
  }),
)}

This works fine as a simple test to see if my service can return a user based on an ID passed in to it. The problem is that I use the findOneOrFail method in other endpoints like save like this:

    async save(createUserDto: CreateUserDto): Promise<User> {
      const newUser = this.usersRepository.create(createUserDto);
      const insertedRecord = await this.usersRepository.insert(newUser);
      return this.findOneOrFail(insertedRecord.identifiers[0].id);
    }

Since the save returns the findOneOrFail method, if I want to test this save method it will always return the value that I set in the mock repository above so I can't test if it will return a new save value or if it returns a new updated value. Am I supposed to make the mock findOneOrFail more generic to handle if it's used in multiple endpoints?

1 Answers

You are creating the mock in your code here:

const mockUsersRepository = {
create: jest.fn().mockImplementation((dto) => dto),
insert: jest.fn((user) => Promise.resolve({ id: 1, ...user })),
findOneOrFail: jest.fn(({ where: { id } }) =>
  Promise.resolve({
    id: id,
    firstName: 'John',
    lastName: 'Smith',
    created: '2022-08-18T04:43:26.035Z',
  }),
)}

The Promise.resolve is what is returning the data for you. I do this a little different, and use the @golevelup/ts-jest library for easy mocking. I highly recommend this for this type of testing.

import { createMock } from '@golevelup/ts-jest'; // Using this to very easily mock the Repository

The beforeEach to create your mock

beforeEach(async () => {
    ...
    const mockUsersRepository = createMock<usersRepository>();  // Create the full mock of the TypeORM Repository

    ...

In the actual test then, the simple negative test for an exception being thrown:

it('should error out if ...', async () => {
    jest.spyOn(mockUsersRepository, 'create').mockImplementation( () => {
        throw new Error('Mock error'); // Just error out, contents are not important
    });
    expect.assertions(3); // Ensure there are 3 expect assertions to ensure the try/catch does not allow code to to bypass and be OK without an exception raised
        try {
            await service.save(...);
        } catch (Exception) {
            expect(Exception).toBeDefined();
            expect(Exception instanceof HttpException).toBe(true);
            expect(Exception.message).toBe('Mock error');
        }
    });
});
    

In another test, that requires two steps, you can use the mockResolvedValueOnce() multiple times. For instance, maybe the call to the actual database function needs to read twice, once that it does not exist, and then once that it does. This next test covers that the code being called will do 2 findAll calls.

mockResolvedValueOnce('A').mockResolvedValueOnce('B').mockResolvedValueOnce('C') will return the 'A' for the first call, 'B' for the second, and 'C' for the 3rd and any additional calls. You can also chain mockRejectValueOnce() and others.

In your save function, what I have found is you should simply test the save() function, and have the usersRepository mocked. This way you only need to test the return from this function, without all the work against a data source. Therefore, the mock is on the save() and you simply return the instance of the User that you want.

async save(createUserDto: CreateUserDto): Promise<User> {
  const newUser = this.usersRepository.create(createUserDto);
  const insertedRecord = await this.usersRepository.insert(newUser);
  return this.findOneOrFail(insertedRecord.identifiers[0].id);
}

However, if you want to mock just the storage calls, then the usersRepository is what you need to mock. Then you need to create the mockResolvedValueOnce (or mockResolvedValue() for all calls), against the .create() method, something against the insert() method, and then something against the findOneOrFail() method. All of these are called, so you mock needs to handle all 3.

In these examples, the mockResolvedValueOnce() is simply declaring the return, without having to do the implementation you have done via jest.fn, and mockImplementation.

Related