Executando verificação de segurança...
11

Na prática: TypeScript + ESLint + Prettier + EditorConfig + Jest, Supertest...


Este material tem como finalidade apresentar de forma prática como configurar a suíte de ferramentas mencionadas no titulo deste material. Ou seja, este material não visa ser didático, mas sim prático. Paralelamente a isso, estou criando uma série de documentos com abordagem didática sobre o mesmo tema…

📚 Livros

No meu repositório no GitHub, você encontrará um link para o download de alguns livros publicados por mim. Além disso, poderá acompanhar o desenvolvimento das minhas obras mais recentes, como o livro sobre Node.js para iniciantes. Acesse: https://github.com/fabiojaniolima/livros

🔗 Sugestão de leitura

Os posts a seguir são meras sugestões ou recomendações de leitura e não devem ser considerados pré-requisitos para a leitura ou execução das práticas descritas neste material:

Dica: Recomendo fortemente o uso do ESLint, Prettier e EditorConfig em todos os projetos, pois é uma boa prática manter a consistência em diversos aspectos.

❗️Pré-requisitos

  • Node.js
  • NPM, Yarn…
  • VSCode*

Ao longo deste material, utilizarei o NPM como gerenciador de pacotes. No entanto, você está livre para usar o Yarn ou qualquer outro gerenciador de sua preferência. Quanto ao editor de código, farei uso do VSCode, mas sinta-se à vontade para escolher o editor que mais lhe agrada.

📦 Pacotes utilizados

  • typescript
  • tsx
  • supertest
  • rimraf
  • eslint
  • prettier
  • jest
  • dotenv
  • cors
  • express

Observe que optei pelo ESLint em substituição ao TSLint. Esta escolha deve-se ao fato de que, atualmente, o ESLint oferece suporte completo às funcionalidades do TypeScript. Adicionalmente, a equipe de desenvolvimento do TSLint tem incentivado essa migração e concentrado esforços no desenvolvimento do ESLint.

🧑‍💻 Mãos a obra

Para os passos a seguir, estou partindo do pressuposto de que você já inicializou um diretório como um projeto Node. Por exemplo:

 npm init -y

EditorConfig

Este é um daqueles plugins de uso geral, ou seja, independe da tecnologia. Confira minha configuração de uso geral:

root = true

[*]
charset = utf-8
indent_size = 2
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
indent_size = 4

Para saber mais, leia meu artigo: EditorConfig - Padronizando características essenciais

Configurações iniciais

Vamos começar instalando nossas primeiras dependências de desenvolvimento:

npm install typescript @types/node rimraf tsx -D
  • typescript: instala o TypeScript e utilitário tsc.
  • @types/node: oferece definições de tipos para o conjunto nativo de APIs/módulos do NodeJS.
  • rimraf: será usado como um utilitário para excluir automaticamente builds antigas.
  • tsx: permite executar TypeScript em ambiente de desenvolvimento.

Para gerar o tsconfig.json (arquivo de definições do TypeScript), execute o comando a seguir:

npx tsc --init

O arquivo gerado é autoexplicativo, contendo configurações e suas respectivas explicações em comentários. Edite o arquivo tsconfig.json, mantendo apenas as configurações a seguir:

{
  "compilerOptions": {
    /* Language and Environment */
    "target": "es2021",
    "lib": ["ES2021"],

    /* Modules */
    "module": "commonjs",
    "rootDir": "./src",

    /* JavaScript Support */
    "allowJs": true,
    "checkJs": true,

    /* Emit */
    "outDir": "./build",
    "sourceMap": false,
    "removeComments": true,

    /* Interop Constraints */
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,

    /* Type Checking */
    "strict": true,
    "skipLibCheck": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "build"]
}

Edite o arquivo package.json e adicione o alias de build ao bloco scripts:

{
  "name": "project",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "build": "rimraf ./build && tsc",
    //...omitido

⚠️ Após a configuração do arquivo tsconfig.json, é comum se deparar com um erro do seguinte tipo:

No inputs were found in config file '/home/fabio/project/tsconfig.json'. Specified 'include' paths were '["src/**/*"]' and 'exclude' paths were '["node_modules","build"]'.

Caso isso ocorra, não há motivo para preocupação. Esse erro acontece porque, até o momento, não existe código no projeto, e o transpiler está sinalizando justamente essa ausência.

ESLint e Prettier

Para assegurar uma boa sintaxe e formatação, é essencial utilizar estes dois recursos em conjunto. Para instalá-los, execute o seguinte comando:

npm install eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-import eslint-plugin-simple-import-sort -D
  • eslint: responsável pelo lint do código, verifica erros de sintaxe, formatação e outros aspectos.
  • prettier: focado na formatação e padronização do código.
  • eslint-config-prettier: desativa as regras do ESLint que podem entrar em conflito com o Prettier.
  • eslint-plugin-prettier: permite executar o Prettier como uma regra do ESLint.
  • eslint-plugin-import: aplica regras de agrupamento e validação para imports e exports.
  • eslint-plugin-simple-import-sort: organiza os imports em ordem alfabética.

Execute o comando abaixo para gerar o arquivo de configuração .eslintrc.*:

npm init @eslint/config

Reproduza as respostas conforme abaixo:

  1. O ESLint será configurado com foco na verificação de sintaxe e detecção de problemas:

Imagem 01: estratégia de uso do ESLint

  1. Utilizaremos ECMAScript Modules (import/export):

Imagem 02: estratégia de importação de módulos

  1. Não utilizaremos nenhum framework:

Imagem 03: framework nativamente suportados

  1. Utilizaremos TypeScript:

imagem 04: Projeto baseado em TypeScript

  1. Nossa app é uma aplicação backend, ou seja, roda do lado do servidor com NodeJS:

Imagem 05: plataforma onde o projeto irá rodar

  1. O arquivo de configuração do ESLint será gerado em formato JSON:

Imagem 06: formato do arquivo de configurações

  1. Pode ser necessário instalar alguns pacotes adicionais, sendo assim selecione Yes:

Imagem 07: pacotes de definições de tipos do ESLint

  1. Selecione seu gerenciador de pacotes:

Imagem 08: gerenciador de pacotes

Edite o arquivo .eslintrc.json e ajuste os blocos extends, plugins, ignorePatterns e rules conforme abaixo:

{
  "env": {
    "es2021": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:prettier/recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint", "import", "simple-import-sort"],
  "ignorePatterns": ["build", "coverage", "package-lock.json"],
  "rules": {
    "import/first": "error",
    "import/newline-after-import": "error",
    "import/no-duplicates": "error",
    "simple-import-sort/imports": "error",
    "simple-import-sort/exports": "error"
  },
  "overrides": [
    {
      "files": ["*.js"],
      "rules": {
        "@typescript-eslint/no-var-requires": "off"
      }
    }
  ]
}

Uma vez que o arquivo de configurações do ESLint está pronto, é hora de configurar as regras que desejamos aplicar no Prettier. Para configurar o Prettier, crie na raiz do projeto um arquivo chamado .prettierrc.json com o seguinte conteúdo:

{
  "semi": true,
  "tabWidth": 2,
  "printWidth": 80,
  "singleQuote": true,
  "arrowParens": "always",
  "trailingComma": "all"
}

As configurações mencionadas acima refletem apenas uma preferência pessoal para formatação estética. Portanto, sinta-se à vontade para desenvolver seu próprio estilo, caso prefira. Abaixo, explico o significado de cada uma das opções utilizadas:

  • semi: quando true, indica que a linha deve terminar com ponto e vírgula
  • tabWidth: define o número de espaços utilizados para a indentação.
  • printWidth: estabelece o comprimento máximo da linha de código antes de aplicar uma quebra de linha.
  • singleQuote: quando true, especifica o uso de aspas simples como padrão.
  • arrowParens: sempre utiliza parênteses ao redor de um único parâmetro em funções arrow.
  • trailingComma: adiciona vírgulas ao final de listas multilinhas, facilitando a adição de novos itens sem esquecer da vírgula anterior.

Observação: a lista completa de regras/opções disponíveis pode ser encontrada no link: https://prettier.io/docs/en/options.html.

Edite o package.json para incluir um novo alias/script chamado lint:fix conforme abaixo:

{
  "name": "project",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "build": "rimraf ./build && tsc",
    "lint:fix": "eslint --fix --ext .js,.mjs,.ts,.json .",
    //...omitido

A instrução lint:fix possibilita a execução do ESLint via terminal, isto é, em modo CLI (Interface de Linha de Comando). Para habilitar a funcionalidade de correção automática ao salvar um arquivo (autoFixOnSave), é necessário configurar essa opção no seu editor de código.

Jest

Instalando dependências:

npm install jest ts-jest eslint-plugin-jest @types/jest -D

Para criar o arquivo de definições do Jest (jest.config.js), execute:

npx jest --init

Um questionário será exibido. Responda a este questionário de acordo com as referências fornecidas abaixo:

Imagem 09: steps de configuração de Jest

O passo 4 possibilita trabalhar com a cobertura de código. Isso significa que podemos especificar caminhos a serem monitorados pelo Jest, que, por sua vez, irá reportar a porcentagem do código coberta por testes e identificar as partes não cobertas. Caso prefira não utilizar esse recurso, basta responder 'n'.

Prossiga editando o arquivo jest.config.js. Localize as variáveis preset e testMatch e defina-as com os valores a seguir:"

preset: 'ts-jest',
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],

Na configuração acima estamos buscando testes que estejam dentro de um diretório __tests__, não importa onde esse esteja dentro do projeto, arquivos *.spec.ts ou *.test.ts.

Agora, edite o arquivo .eslintrc.json e adicione a propriedade "jest": true conforme abaixo:

{
  "env": {
    "es2021": true,
    "node": true,
    "jest": true
		//...omitido

Caso deseje utilizar a funcionalidade de Cobertura de Código do Jest, será necessário editar o arquivo jest.config.js. Localize e defina as variáveis da seguinte maneira:

collectCoverageFrom: ['<rootDir>/src/services/**/*.ts'],
coverageReporters: ["text-summary", "lcov"],

No campo collectCoverageFrom, informe um array com os caminhos dos arquivos que devem ser incluídos na cobertura. Por exemplo, para cobrir outro diretório no mesmo nível de services, adicione uma expressão regular: <rootDir>/src/{services,helpers}/**/*.ts. Para mais padrões de expressões regulares, consulte a documentação oficial.

Os resultados da Cobertura de Código serão salvos no diretório especificado pela variável coverageDirectory. Não se esqueça de adicionar este diretório ao .gitignore.

Agora, crie o arquivo __tests__/sum.ts e insira o seguinte conteúdo:

describe('Only one test', () => {
  it('Sum 1 + 1 = 2', () => {
    expect(1 + 1).toBe(2)
  })
})

O arquivo mencionado é utilizado exclusivamente para verificar se o Jest está operando corretamente. Agora, proceda com a execução do seguinte comando:

npm run test

Como resultado, teremos o seguinte output na console:

Imagem 10: output do Jest

Vamos avaliar cada uma das partes retornadas:

  1. Resultado de cada uma das etapas.
  2. Resumo da Cobertura de Testes (Coverage Summary). Lembre-se de que, no arquivo jest.config.js, configuramos a opção collectCoverageFrom. O Coverage Summary calcula a cobertura de testes para os caminhos especificados nessa configuração.
  3. Resumo do teste

Realmente é impressionante! No entanto, a cereja do bolo se encontra em ./coverage/lcov-report/index.html. Abra esse arquivo no seu navegador para visualizar a cobertura dos seus testes.

Como ainda não temos código sendo testado em nossa aplicação, o resultado será uma página sem dados de cobertura.

Agora, será necessário editar o tsconfig.json e adicionar coverage, __tests__ e **/*.spec.ts à lista de itens a serem ignorados:"

"exclude": [
    "node_modules",
    "build",
    "coverage",
    "**/__tests__",
    "**/*.spec.ts",
    "**/*.test.ts"
  ]

Scripts e rotinas de execução (package.json)

Edite o package.json, localize a sessão main e scripts e altere estas conforme abaixo:

"main": "./build/start/server.js",
"scripts": {
  "build": "rimraf ./build && tsc",
  "start:dev": "tsx ./src/start/server.ts",
  "start:inspect": "tsx --inspect-brk ./src/start/server.ts",
  "start:watch": "tsx watch ./src/start/server.ts",
  "lint:fix": "eslint --fix --ext .js,.mjs,.ts,.json .",
  "test": "jest"
}
  • main: define o ponto de entrada da aplicação. Aqui, ele aponta para o diretório de build, ou seja, ao executar node ., este arquivo será o inicializado.
  • build: emprega o rimraf para remover o diretório ./build e, em seguida, compila os arquivos JavaScript usando o tsc.
  • start:dev: inicializa o servidor em modo desenvolvimento.
  • start:inspect: inicializa o servidor em modo desenvolvimento com inspect ativo para debug — no final deste material mostrarei como fazer o debug pelo VSCode.
  • start:watch: inicia o servidor de desenvolvimento em modo de observação ('watch').
  • lint:fix: habilita a execução do ESLint através do terminal.
  • test: executa o Jest.

Para rodar qualquer um dos scripts definidos, utilize o comando:

npm run nome_do_script

Por exemplo:

npm run lint:fix

⚠️ Como nosso diretório ./src ainda não contém arquivos TypeScript, a execução de qualquer outro script resultará em erro. Não se preocupe, pois isso é esperado."

Express

Vamos as dependências:

npm install express dotenv cors
  • express: micro-framework simples e flexível;
  • cors (cross-origin): é uma especificação que define meios para que um recurso do servidor seja acessado remotamente via web/rede; Resumidamente, o cors permite que nossa aplicação seja acessada de um endereço externo;
  • dotenv: é um módulo de dependência zero, responsável por carregar variáveis de ambiente de um arquivo .env em process.env.*.

Será necessário instalar as definições de tipo do Express:

npm install @types/express @types/cors -D

Agora crie na raiz do projeto um arquivo .env e atribua o valor abaixo a ele:

APP_PORT=3000

Este arquivo irá centralizar informações sensíveis da aplicação e por isso não é versionado. Crie um arquivo .env-sample de amostra, este pode conter informações pre-preenchidas não sensíveis. Exemplo:

DB_HOST="localhost"
DB_PORT=5432
DB_NAME=
DB_USER=
DB_PASS=

Crie o arquivo de rotas ./src/routes.ts:

import { Router } from 'express';

const router = Router();

router.get('/', (_, res) => {
  res.json({ message: 'Hello world!' });
});

export default router;

Agora crie o arquivo ./src/start/app.ts com o seguinte conteúdo:

import 'dotenv/config'
import cors from 'cors'
import express, { Errback, Request, Response } from 'express'

import router from '../routes'

const app = express()

app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: false }))

app.use(router)

app.use((_: Request, res: Response) => {
  res.status(404).json({ message: 'Page Not Found' })
})

app.use((_: Errback, _req: Request, res: Response) => {
  return res.status(500).json({ message: 'Internal Error' })
})

export default app

Crie o arquivo ./src/start/server.ts, esse arquivo é o responsável por subir o servidor na porta informado no arquivo .env:

import app from './app'

const port = parseInt(process.env.APP_PORT as string) || 3000

app.listen(port, () => {
  console.log('\x1b[33m%s\x1b[0m', `=> 🚀 Server running on the port: ${port}`)
})

Para testar o resultado basta executar na raiz do projeto:

npm run start:dev

Se tudo correu bem teremos como retorno o seguinte output:

Imagem 11: output do start do servidor de desenvolvimento

Show de bola! Nossa aplicação está rodando na porta 3000, para acessar basta chamar: http://localhost:3000 e teremos um lindo e belo JSON como output:

{ "message": "Hello World!" }

Supertest

Bom, nossa aplicação com Express é extremamente simples, porém, suficiente para realizarmos testes reais com o Jest. Nossa missão é disparar uma requisição contra a rota raiz (GET /) e verificar se o status de retorno é 200 e se a propriedade message foi devolvida.

Para facilitar nossa vida vamos instalar uma lib projetada para lidar com testes que envolvem requisições HTTP:

npm install supertest @types/supertest -D

Você pode excluir o arquivo de teste __tests__/sum.ts se preferir, afinal de contas ele tinha um único objetivo, verificar se o Jest estava de fato funcionando.

Edite novamente o arquivo jest.config.js e:

...// Por conta de limitação aqui da plataforma (body < 20000 caracteres) eu não consigo postar o conteúdo completo. Continue lendo em: https://fabiojanio.medium.com/na-prática-typescript-eslint-prettier-editorconfig-jest-supertest-7206684ab55d

Carregando publicação patrocinada...