DEV Community

Cover image for Como Escrever Testes Unitários para Serviços Backend com Dependências de Banco de Dados Usando SQLite In-Memory
Vitor Rios
Vitor Rios

Posted on

Como Escrever Testes Unitários para Serviços Backend com Dependências de Banco de Dados Usando SQLite In-Memory

Introdução

Ao desenvolver serviços backend, os testes unitários são cruciais para garantir a correção e a estabilidade do seu código. No entanto, escrever testes unitários para componentes que interagem com um banco de dados pode ser desafiador. Usar um banco de dados real para testes pode ser lento e complicado, além de introduzir efeitos colaterais que dificultam a reprodução dos testes. Uma solução eficaz é usar um banco de dados SQLite in-memory, que é rápido e fácil de configurar, permitindo que os testes sejam isolados e repetíveis.

Neste artigo, vamos explorar como configurar e escrever testes unitários para um serviço backend que interage com um banco de dados, usando TypeORM e SQLite in-memory.

Instalação das Dependências

Primeiro, precisamos instalar as dependências necessárias:

npm install --save-dev typescript ts-jest ts-node @types/jest @types/node jest sqlite3 typeorm reflect-metadata
Enter fullscreen mode Exit fullscreen mode

Configuração do Ambiente de Testes

Arquivo de Configuração do TypeORM para Testes

Crie um arquivo de configuração do TypeORM específico para testes. Este arquivo configura o TypeORM para usar um banco de dados SQLite em memória.

jest.setup.ts

import 'reflect-metadata';
import {
  createConnection,
  getConnection,
} from 'typeorm';
import { User } from './src/entity/User';

beforeAll(() => {
  return createConnection({
    type: 'sqlite',
    database: ':memory:',
    dropSchema: true,
    entities: [User],
    synchronize: true,
    logging: false,
  });
});

afterAll(async () => {
  const connection = getConnection();
  await connection.close();
});

afterEach(async () => {
  const connection = getConnection();
  await connection.synchronize(true);
});
Enter fullscreen mode Exit fullscreen mode

Estrutura do Projeto

Suponha que você tenha a seguinte estrutura de projeto:

src/
  entity/
    User.ts
  repository/
    UserRepository.ts
  service/
    UserService.ts
  __tests__/
    UserService.test.ts
  jest.setup.ts
Enter fullscreen mode Exit fullscreen mode

Implementação dos Módulos

src/entity/User.ts

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column()
  name!: string;

  @Column()
  email!: string;
}
Enter fullscreen mode Exit fullscreen mode

src/repository/UserRepository.ts

import { EntityRepository, Repository } from 'typeorm';
import { User } from '../entity/User';

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  findByName(name: string): Promise<User | undefined> {
    return this.findOne({ name });
  }
}
Enter fullscreen mode Exit fullscreen mode

src/service/UserService.ts

import { getCustomRepository } from 'typeorm';
import { UserRepository } from '../repository/UserRepository';
import { User } from '../entity/User';

export class UserService {
  private userRepository = getCustomRepository(UserRepository);

  async findUserByName(name: string): Promise<User | undefined> {
    return this.userRepository.findByName(name);
  }

  async createUser(name: string, email: string): Promise<User> {
    const user = new User();
    user.name = name;
    user.email = email;
    return this.userRepository.save(user);
  }
}
Enter fullscreen mode Exit fullscreen mode

Configuração de Testes com Jest

Crie um arquivo de configuração do Jest para garantir que o ambiente de testes está configurado corretamente.

jest.config.js

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  setupFilesAfterEnv: ['./jest.setup.ts'],
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  testPathIgnorePatterns: ['/node_modules/', '/dist/'],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
  globals: {
    'ts-jest': {
      tsconfig: 'tsconfig.json',
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Configuração do TypeScript

Certifique-se de que a configuração do TypeScript (tsconfig.json) permite o uso de decoradores e metadados de decoradores.

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "**/*.test.ts", "dist"]
}
Enter fullscreen mode Exit fullscreen mode

Escrita dos Testes

src/__tests__/UserService.test.ts

import { UserService } from '../service/UserService';

describe('UserService', () => {
  let userService: UserService;

  beforeAll(() => {
    userService = new UserService();
  });

  test('should create a new user', async () => {
    const user = await userService.createUser('John Doe', 'john@example.com');
    expect(user).toHaveProperty('id');
    expect(user.name).toBe('John Doe');
    expect(user.email).toBe('john@example.com');
  });

  test('should find a user by name', async () => {
    const user = await userService.createUser('Jane Doe', 'jane@example.com');
    const foundUser = await userService.findUserByName('Jane Doe');
    expect(foundUser).toBeDefined();
    expect(foundUser?.name).toBe('Jane Doe');
    expect(foundUser?.email).toBe('jane@example.com');
  });

  test('should return undefined if user is not found', async () => {
    const foundUser = await userService.findUserByName('Non Existent');
    expect(foundUser).toBeUndefined();
  });
});
Enter fullscreen mode Exit fullscreen mode

Explicação

  1. Configuração do Banco de Dados de Teste:

    • Configuramos o TypeORM para usar um banco de dados SQLite em memória para testes.
    • jest.setup.ts é usado para criar a conexão com o banco de dados antes de todos os testes e fechá-la após todos os testes.
  2. Sincronização do Banco de Dados:

    • Após cada teste, sincronizamos o banco de dados para limpar os dados inseridos durante o teste (await getConnection().synchronize(true);).
  3. Escrita dos Testes:

    • Criamos testes para verificar se o usuário é criado corretamente, se o usuário é encontrado pelo nome e se retorna undefined quando o usuário não é encontrado.

Conclusão

Usar um banco de dados SQLite in-memory para testes é uma ótima maneira de testar funcionalidades que dependem de um banco de dados real sem a complexidade de configurar um banco de dados de teste separado. Isso garante que os testes sejam rápidos, isolados e confiáveis. Com esta abordagem, você pode garantir que seu código interaja corretamente com o banco de dados e que todas as funcionalidades críticas sejam verificadas.

Repositório

https://github.com/vitorrios1001/tests-with-db

Top comments (0)