Testing with NestJS like a Pro
Tags: nestjs, node, jest, typescript
Published: 2022-04-08 02:29:12

Hello folks,

I've been using **[NestJS](https://nestjs.com/)** for the last few years and it's an amazing framework for **NodeJS** and **[ExpressJS](https://expressjs.com/)**. I personally love using **[TypeScript](https://www.typescriptlang.org/)** on the _[Backend](https://en.wikipedia.org/wiki/Frontend_and_backend)_ and having a good [_Separation of concerns (SoC)_](https://en.wikipedia.org/wiki/Separation_of_concerns)  while I'm creating my services, _following a technology-agnostic approach for the win!_

But sometimes it's difficult to do certain things, especially the tests for our code, which is something that's hardly mentioned [(even in documentation)](https://docs.nestjs.com/techniques/database#testing). This is why when you want to use **[TypeORM](https://typeorm.io/)** to perform more advanced operations (revert SQL transactions if something fails, etc) it can become difficult to test our code.

Likewise, sometimes it's difficult to mock all these external dependencies of our services and it becomes somewhat tedious, so we stop enjoying working with these tools that should make our lives easier instead of wanting to change our entire development.

That is why in this post I want to show you an amazing library to create mocks of our dependencies using **[Jest](https://jestjs.io)** and how to use a design pattern such as **[Singleton](https://en.wikipedia.org/wiki/Singleton_pattern)**, which has helped me a lot when testing my code.

Imagine we have this strange **microservice**:
```ts
import { WRITE_CONNECTION } from '@my-api/common';
import { Injectable, Logger } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
import { InjectEntityManager } from '@nestjs/typeorm';
import { EntityManager } from 'typeorm';

@Injectable()
export class MyService {
  private logger = new Logger(MyService.name);
  constructor(
    @InjectEntityManager(WRITE_CONNECTION) private entityManager: EntityManager,
  ) {}

  async saveSomething(data: string): Promise<void> {
    try {
      return await this.entityManager.transaction(async (entityManager) => {
        const firstRepository = entityManager.getCustomRepository(FirstRepository);
        const secondRepository = entityManager.getCustomRepository(SecondRepository);

        const firstRecord = firstRepository.create({ data });
        await firstRepository.save(firstRecord);

        const secondRecord = secondRepository.create({ data });
        await secondRepository.save(secondRecord);

        // Save entities directly
        await entityManager.save([...]);
      });
    } catch (error) {
      this.logger.error(`Failed saving something, error ${error.message}`, error.stack);
      throw new RpcException(error.message);
    }
  }
}
```

Now let's see what it would be like to test our code:

```ts
import { createMock } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { EntityManager } from 'typeorm';

describe('MyService', () => {
  let service: MyService;
  let entityManager: EntityManager;
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        MyService,
        { provide: EntityManager, useValue: createMock<EntityManager>() },
      ],
    }).compile();

    service = module.get(CatalogsService);
    entityManager = module.get(EntityManager);
  });
});
```

As we can see, it's very easy to mock these external services such as the **TypeORM** **[EntityManager](https://github.com/typeorm/typeorm/blob/master/docs/entity-manager-api.md)**, etc. using the **[createMock](https://github.com/golevelup/nestjs/tree/master/packages/testing#motivation)** function provided by **[Go Level Up](https://github.com/golevelup)**, which automatically injects [Jest mock functions](https://jestjs.io/docs/mock-functions) to replace external implementations on which our code depends, which in a unit test should not matter.

```ts
import { createMock } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { EntityManager } from 'typeorm';

describe('MyService', () => {
  ...
  
  it('should call a transaction correctly', async () => {
    const mockEntityManager = {
      save: jest.fn(),
      getCustomRepository: jest.fn((fn) => mockEntityManager[fn] || (mockEntityManager[fn] = createMock<typeof fn>())),
    };
    const spyTransaction = (entityManager.transaction as jest.Mock).mockImplementation((cb) => cb(mockEntityManager));
    const firstRepo: FirstRepository = mockEntityManager.getCustomRepository(SubCatalogRepository);
    const secondRepo: SecondRepository = mockEntityManager.getCustomRepository(SecondRepository);
    await service.saveSomething('MOCK DATA');

    expect(spyTransaction).toHaveBeenCalled();
    expect(firstRepo.save).toHaveBeenCalled();
    expect(secondRepo.save).toHaveBeenCalled();
    expect(mockEntityManager.save).toHaveBeenCalled();
  });
```

Then we have the possibility of using the **[EntityManager](https://typeorm.io/working-with-entity-manager#what-is-entitymanager)** to create transactions with several repositories that can execute a [_rollback_](https://en.wikipedia.org/wiki/Rollback_(data_management)) automatically if any of these operations fail, and in the tests we use the **Singleton** pattern to define a mock of this entity that allows us to return the same instance of these repositories to test that all these read and write operations have been performed as expected.

Remember that in our tests it's also important to test not only the **[happy path](https://en.wikipedia.org/wiki/Happy_path)**, but all kinds of scenarios where our code can fail due to some invalid or not allowed operation. For this, with [Jest](https://jestjs.io/docs/tutorial-async) we have utilities where we can easily test these asynchronous cases. e.g:

- **my-service.ts**:
```ts
@Injectable()
export class MyService {
  private logger = new Logger(MyService.name);

  constructor(
    private myRepository: MyRepository,
  ) {}

  async donwloadReport(recordId: number): Promise<string> {
    try {
      const record = await this.myRepository.findOne(recordId);
      if (!record) {
        throw new Error('Record not found');
      }
      // TODO: generate CSV file or something like that
      return 'export';
    } catch (error) {
      this.logger.error(`Failed generating a report, error ${error.message}`, error.stack);
      throw new RpcException(error.message);
    }
  }
}
```
- **my-service.spec.ts**:
```ts
describe('MyService', () => {
  let service: MyService;
  let repository: MyRepository;
  let logger: Logger;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        MyService,
        { provide: MyRepository, useValue: createMock<MyRepository>() },
      ],
    }).compile();

    service = module.get<ProductsService>(ProductsService);
    repository = module.get(BrandRepository);
    logger = service['logger'] = createMock<Logger>();
  });

  it('should throw an error when downloading a report of a record that does not exist', async () => {
    const errorMessage = 'Record not found';
    const spyFindOne = (repository.findOne as jest.Mock).mockImplementationOnce(() => Promise.resolve(null));
    const recordId = -1;
    await expect(service.downloadReport(recordId)).rejects.toThrow(new RpcException(errorMessage));
    expect(spyFindOne).toHaveBeenCalledWith(recordId);
    expect(logger.error).toHaveBeenCalled();
  });
});
```

Using [expect().rejects](https://jestjs.io/docs/expect#rejects) we can wait for our [asynchronous code](https://en.wikipedia.org/wiki/Ajax_(programming)) to fail and throw an exception handled by our code, thus avoiding undesirable scenarios where the client is responded with an **[Internal Server Error](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors)** which was not anticipated by a [noob](https://en.wikipedia.org/wiki/Newbie) [developer](https://en.wikipedia.org/wiki/Programmer).

**Bonus:**

If you want to learn more about design patterns, don't forget to take a look at [Design Patterns for Humans](https://github.com/kamranahmedse/design-patterns-for-humans), it's an incredible repository with many interesting examples that you can apply when you want to use a design pattern to solve a specific problem.

I hope you find this example useful for your projects, and let's continue improving our services with NestJS! 😊

## Supporting 🍻

I believe in Unicorns 🦄 Support [me](http://www.paypal.me/jdnichollsc/2), if you do too.


Made with ❤️

J.D. Nicholls