Design Patterns: Developing Testable APIs with Node.js

Posted by


Criando APIs testáveis com Node.js (Design Patterns)

Introdução:
Neste tutorial, vamos aprender como criar APIs testáveis usando Node.js, focando em design patterns que nos ajudam a escrever código limpo e modular. Testabilidade é uma característica muito importante em desenvolvimento de software, pois nos permite garantir que nosso código funcione corretamente e que possamos fazer alterações sem medo de quebrar outras partes do sistema.

Vamos usar o Node.js como nossa plataforma de desenvolvimento, pois ele é uma excelente escolha para a construção de APIs devido à sua flexibilidade, velocidade e fácil integração com bancos de dados e sistemas externos.

Design Patterns:
Antes de começarmos a programar, é importante entender alguns design patterns que são úteis ao construir APIs testáveis em Node.js.

  1. MVC (Model-View-Controller): Este é um padrão de arquitetura muito comum em desenvolvimento web. Ele divide a aplicação em três partes distintas: o Model (modelo), que representa os dados da aplicação; o View (visão), que é a interface com o usuário; e o Controller (controlador), que coordena a interação entre o modelo e a visão. Este padrão ajuda a separar as preocupações e torna o código mais organizado e fácil de manter.

  2. Dependency Injection: Este padrão nos permite injetar as dependências de uma classe em vez de criá-las dentro da própria classe. Isso facilita a escrita de testes, pois podemos substituir facilmente as dependências reais por mocks ou stubs durante os testes.

  3. Factory Pattern: Este padrão nos permite instanciar objetos sem precisar conhecer os detalhes de como eles são criados. Isso é útil quando queremos desacoplar a criação de objetos e fornecer uma interface mais flexível para instanciá-los.

Estrutura do projeto:
Vamos começar criando a estrutura básica do nosso projeto. Para simplificar, vamos dividir a aplicação em três pastas principais: models (para os modelos de dados), controllers (para os controladores da API) e routes (para as rotas da API). Além disso, criaremos uma pasta tests para os testes unitários da aplicação.

Dentro do projeto, teremos os seguintes arquivos:

  • app.js: arquivo principal da aplicação que inicializa o servidor e configura as rotas
  • models/: pasta para os modelos de dados
  • controllers/: pasta para os controladores da API
  • routes/: pasta para definir as rotas da API
  • tests/: pasta para os testes unitários

Construindo a API:
Vamos agora começar a construir a nossa API utilizando os design patterns mencionados anteriormente.

  1. Model:
    Vamos criar um modelo de dados simples para representar um usuário. Para isso, crie um arquivo user.model.js dentro da pasta models com o seguinte conteúdo:
class UserModel {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

module.exports = UserModel;
  1. Controller:
    Agora vamos criar um controlador para lidar com as requisições relacionadas aos usuários. Para isso, crie um arquivo user.controller.js dentro da pasta controllers com o seguinte conteúdo:
const UserModel = require('../models/user.model');

class UserController {
  constructor(userModel) {
    this.userModel = userModel;
  }

  async createUser(name, email) {
    const newUser = new this.userModel(name, email);
    // salvar usuário no banco de dados
    return newUser;
  }
}

module.exports = UserController;
  1. Routes:
    Agora vamos definir as rotas da nossa API. Para isso, crie um arquivo user.routes.js dentro da pasta routes com o seguinte conteúdo:
const express = require('express');
const UserController = require('../controllers/user.controller');

const router = express.Router();
const userController = new UserController();

router.post('/user', async (req, res) => {
  const { name, email } = req.body;
  const newUser = await userController.createUser(name, email);
  res.json(newUser);
});

module.exports = router;
  1. App.js:
    Por último, vamos configurar as rotas da nossa API no arquivo app.js:
const express = require('express');
const userRoutes = require('./routes/user.routes');

const app = express();

app.use(express.json());
app.use(userRoutes);

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Testando a API:
Agora que já construímos a estrutura básica da nossa API, vamos escrever alguns testes unitários para garantir que tudo esteja funcionando corretamente. Para isso, crie um arquivo user.controller.test.js dentro da pasta tests com o seguinte conteúdo:

const UserController = require('../controllers/user.controller');

describe('UserController', () => {
  let userController;

  beforeEach(() => {
    userController = new UserController();
  });

  test('createUser should return a new user object', async () => {
    const newUser = await userController.createUser('John Doe', 'john@example.com');
    expect(newUser.name).toBe('John Doe');
    expect(newUser.email).toBe('john@example.com');
  });
});

Agora podemos rodar os testes usando um runner de testes como o Jest para confirmar que nossa API está funcionando corretamente.

Conclusão:
Neste tutorial, aprendemos como criar APIs testáveis com Node.js usando design patterns como MVC, Dependency Injection e Factory Pattern. Estes padrões nos ajudam a escrever código limpo e modular que é fácil de testar e manter. Espero que você tenha achado este tutorial útil e esteja pronto para aplicar estes conceitos em seus próprios projetos. Boa sorte e feliz codificação!

0 0 votes
Article Rating
18 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
@leandromoraes1862
2 months ago

Qual a vantagem de se usar funcao nos useCases e controllers? Classes nao seria melhor? Achei interessante o uso de funcoes

@leleparanormal1579
2 months ago

QUERO ESSA CAMISA!

@marciusbezerra
2 months ago

seria melhor que fosse usado interface e injeção de dependência, isso é costume ser feito em c#, causa o mesmo efeito de desacoplamento no final

@trashmanmt3864
2 months ago

Deixei pra assistir a live depois e cortaram a explicação toda… ¬¬

@ebr182
2 months ago

Uma questão mais filosófica do que prática que costumo discutir com colegas é se testes de regra de negócio (testes de use cases) devem checar estado de repositório em memória (como o Diego mostra no tempo 1:00:50). Particularmente defendo que não devem, porque na prática isso testa a implementação do repositório em memória ao invés de testar a regra de negócio em si, acredito que assim como o use case não deve conhecer a implementação do repositório, o teste do use case também não deve conhecer o estado/implementação do repositório em memória, pois o que faz parte da regra de negócio é a chamada do método do repositório (isso sim deve ser testado e deve ser o suficiente). Quando se testa o estado do repositório em memória, a partir de um teste de use case, está se testando um side effect de uma classe que não importa para a regra de negócio, já que estamos usando inversão de controle e injeção de dependência.

@paolorr
2 months ago

Que comando http eh esse q vc usa no terminal para fazer requisicoes http?

@LeonardoOliveira-tx6by
2 months ago

Injeção de Dependência e Inversão de Controle!

@fernandocosta3307
2 months ago

Qual é o modelo do seu fone Diegão?

@adrianmouzinho9615
2 months ago

Alquém pra me ajudar,
não entendi como o diego usou o arquivo setup-db.js

@sergiodsalles9573
2 months ago

Vendo agora, em 11:53 quando o Diegão fala que o projeto vai estar hoje no Ignite, a vontade que tive foi de sair "correndo" pra uma nova aba e ver o projeto lá… mas eu vou terminar de asssitir esse video aqui que tá bom demais! Como sempre, conteúdo inigualável!

@brunogomes3933
2 months ago

Olá, eu sou novo na programação mas queria tirar uma duvida muito boba, no caso se a tabela que eu utilizo no banco de dados tenha 30 campos, para fazer os testes eu teria que ficar replicando esses 30 campos ? mesmo que seja criando um modelo da tabela em outro arquivo.

@janssenbatista
2 months ago

Mais uma grande aula, Diegão. 🚀

Só uma pequena correção: o "design pattern" que você mencionou não é "inversão de dependência" e sim "injeção de dependência". 🙂

@hermessantos181
2 months ago

Se o Serasa tivesse a simpatia que todos da Rocket Seat tem eu não teria dívidas

@geisonflores
2 months ago

Diego quando vc diz que os testes melhoram a arquitetura eu entendo que se você está misturando conceitos, no caso design e arquitetura. O design de software está mais relacionado com a implementação específica de um software, enquanto a arquitetura de software está mais relacionada com a estrutura geral do sistema de software. A arquitetura de software é uma atividade mais abrangente e estratégica que fornece a estrutura geral para o desenvolvimento do software, desde a escolha de banco de dados, sistemas de fila e etc. Não tem como guiar escolhas de arquitetura por meio de testes, já design de um sistema ou módulo podem sim emergir dos testes.

@zilondequadrosmaciel1006
2 months ago

Desculpe, só para complementar, a distância ente uma conexão real e uma mokada é qiilometros e quilometros de distância, um abraço.

@zilondequadrosmaciel1006
2 months ago

Diego, simular uma conexao com o banco, indo desde a conexão, passando pela inserção de dados e o retorno para o usuário da mensagem de confirmação, não "PODEM" ser mokados, devem ser feitos de maneira real…, um abraço.

@thiagoporto1635
2 months ago

Qual o nome desse tema do vscode?

@ricardomartins7227
2 months ago

Alguém tem o link do formulário ?