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
Containers no docker
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?
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.
Minha maior dor de cabeça antigamente era configurar testes de integração, estou em shock :)