Testes de integração com Nestjs, Jest, Supertest, Prisma e Testcontainers

Motivação

Bem, não tenho muito costume em fazer esses tipos de publicação, mas como não achei nada especifico com a stack que utilizo no meu dia a dia, então resolvi trazer esse step by step.

Nossa API

Para esse cenário de teste, resolvi criar uma API Rest utilizando o Nestjs juntamente com o Prisma para realizar a persistência dos dados no DB.

Setup para realizar os testes

Certifique-se de ter o Docker e Node rodando na sua máquina


import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import {
  PostgreSqlContainer,
  StartedPostgreSqlContainer,
} from '@testcontainers/postgresql';
import { execSync } from 'child_process';
import { AppModule } from 'src/app.module';
import * as request from 'supertest';

describe('test POST /users', () => {
  let prisma: PrismaClient;
  let app: INestApplication;
  let container: StartedPostgreSqlContainer;

 beforeAll(async () => {
    // iniciando o container docker
    container = await new PostgreSqlContainer().start();

    // configurando a URL de conexão do prisma
    const urlConnection = `postgresql://${container.getUsername()}:${container.getPassword()}@${container.getHost()}:${container.getPort()}/${container.getDatabase()}?schema=public`;

    // definir a URL de conexão para conexãp do prisma
    process.env.DATABASE_URL = urlConnection;

    // criar as tabelas definidas no prisma no banco de dados
    execSync('npx prisma db push', {
      env: {
        ...process.env,
        DATABASE_URL: urlConnection,
      },
    });

    // importar o modúlo que queremos testar
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    // criar a aplicação
    app = moduleRef.createNestApplication();

    // Instanciar o prisma client com a URL de conexão
    prisma = new PrismaClient({
      datasources: {
        db: {
          url: urlConnection,
        },
      },
    });

    // Iniciar a aplicação
    await app.init();
  });

Feito isso já temos nosso setup pronto para iniciar os testes. A biblioteca @testcontainers/postgresql vai abstrair toda parte de inicialização do banco de dados.

Casos de teste

  it('deve criar um novo usuário', async () => {
    // Act
    const response = await request(await app.getHttpServer())
      .post('/users')
      .send({
        email: 'test@mail.com',
        name: 'John Doe',
      });

    // Assert
    const userDb = await prisma.users.findFirst();
    expect(response.statusCode).toBe(201);
    expect(userDb.email).toBe('test@mail.com');
    expect(userDb.name).toBe('John Doe');
  });

  it('deve lançar um erro porque já existe um usuário com esse email', async () => {
    // Act
    const response = await request(await app.getHttpServer())
      .post('/users')
      .send({
        email: 'test@mail.com',
        name: 'John Doe',
      });

    // Assert
    expect(response.statusCode).toBe(422);
  });

  it('deve lançar um erro porque não foram enviados os dados para criação do usuário', async () => {
    // Act
    const response = await request(await app.getHttpServer()).post('/users');

    // Assert
    expect(response.statusCode).toBe(400);
  });

Depois de executarmos todos os testes...

  afterAll(async () => {
    // encerrar o container em execução
    await container.stop();
  });

Executar os testes

npm run test

Nosso terminal

Image description

Containers no docker

Image description

Links

Documentação do testcontainers para o Node: https://node.testcontainers.org/modules/postgresql/

Meu repositório no github: https://github.com/daviArttur/nestjs-testcontainers

Você acha que fazer testes com typeorm ao invés do prisma é melhor ou pior?

Mano, acredito que não seja tão complicado assim não, acho que a parte que mudaria mais seria na hora de fazer o push de entidades, que acho que no prisma seria um apply das migrations, tirando essa parte acredito que o resto é muito parecido
Optei pelo prisma por familiaridade, acredito que o resultado final seja o mesmo.

Muito bacana o setup, com certeza vou testar em algum momento em alguma aplicação minha.

Mas também é possível utilizar uma abordagem um pouco mais enxugada, já que está utilizando Postgres.

Você consegue utilizar dos schemas do Postgres, criar um novo schema para fazer os testes de integração e apagá-lo assim que todos testes forem concluídos. Acho que seria uma maneira sem subir um container inteiro para fazer os testes.

Oi, tentei essa abordagem a um tempo atrás, porém sem sucesso quando passava ultrapassava +10 casos de teste. Recebia um erro muito genérico durante a criação dos schemas junto com o push do schema para o banco, não quis gastar muita energia para tentar resolver na época. A principal dificuldade com o Docker foi o uso de CPU durante os testes. Precisei limitar o número de workers ativos durante a execução. ``` jest --maxWorkers=% ```

Minha maior dor de cabeça antigamente era configurar testes de integração, estou em shock :)