Mocks

External dependencies can be simulated by simple representations.

Inside the src folder, create the file testing/util/testing.util. Here, we can create a type to represent a MockRepository, and a function to instantiate a base mock. Let's then get to it.

Fortunately, with the library we installed earlier, this process will become much simpler. We can use the MockProxy type and pass the Repository with an entity, in order to generate the type for a MockRepository. A generic that extends ObjectLiteral (the same type required in the TypeORM Repository) may prove adequate.

export type MockRepository<TEntity extends ObjectLiteral> = MockProxy<Repository<TEntity>>;

We can then create a function that will generate a generic mock. As the variables that will receive this call will already be properly typed, this function won't need any additional typing.

export const createMock = () => mock();

The result will be the following: we'll get a base mock that may be built upon to provide mocked behavior. Due to the type of the repository, we may only mock its methods and not arbitrary fields, ensuring type safety. These mocked methods will have no logic by default, and for each scenario, a specific behavior can be applied.

Going back to the test file, we shall perform the following steps:

  • Create a variable for the repository below the service

let repository: MockRepository<User>;
  • Call the function to create a base mock

useValue: createMock(),
  • Obtain its reference from the module

repository = module.get(getRepositoryToken(User));

Back in the test for the findOne() method, if we then mock the method that would be called in the repository (and do it before the service call, of course, and assert it to User for a moment), the test will pass!

repository.findOneByOrFail.mockResolvedValueOnce(expectedUser as User);

Some considerations:

  • The method mockReturnValue() makes the chained method return the value

  • By using Resolved, a resolved promise is returned

  • And by using Once, after this call the logic is reset

Before proceeding to the other tests, let's just create three auxiliary functions at the bottom of the file. The faker library will be used to automatically generate fake data. But we need to import it manually.

import { faker } from '@faker-js/faker';

They will be for creating:

  • A fake createUserDto

const genCreateDto = (): CreateUserDto => ({
  name: faker.person.firstName(),
  email: faker.internet.email(),
  phone: faker.phone.number(),
  password: faker.internet.password(),
});
  • A fake updateUserDto

const genUpdateDto = (): UpdateUserDto => ({
  name: faker.person.firstName(),
});
  • A fake user

const genUser = (id: number, createDto = genCreateDto()) =>
  ({
    id,
    ...createDto,
  }) as User;

Note the parameter default in case a createDto is not passed.

With the help of these, we can more easily write our tests. Let's then just use the last one in the test to generate the expectedUser (and remove the type assertion)...

const expectedUser = genUser(id);

...and then proceed to the remaining tests.

Last updated