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

Fábio Jânio
Aug 17 · 11 min read
Image for post
Image for post

Este material tem por finalidade atingir dois objetivos bem simples: tornar clara as vantagens de se utilizar o conjunto de ferramentas citadas no título deste material, bem como demonstrar esse uso na prática. Então sem rodeios vamos ao que interessa.


📚 Livros

Em meu repositório no GitHub você encontrará link para download de alguns livros publicados por mim, bem como poderá acompanhar o desenvolvimento das obras mais recentes, tais como o livro de Node.js para iniciantes. Acesse: https://github.com/fabiojaniolima/livros

🔗 Sugestão de leitura

Os posts abaixo representam tão somente sugestões/recomendações de leitura, não devem ser tidos como pré-requisitos para a leitura ou execução das práticas contidas aqui neste material:

Dica: recomendo o uso do ESLint + Prettier + EditorConfig em todo e qualquer projeto, afinal de contas é uma boa prática manter aspectos de consistência.

❗️Pré-requisitos

  • Node.js
  • NPM ou Yarn
  • VSCode*

Ao longo deste material irei utilizar o Yarn como gerenciador de pacotes, sinta-se livre para utilizar o NPM se assim preferir. Já como editor de código, utilizarei o VSCode, porém, você é livre para utilizar o sabor de editor que preferir.

📦 Pacotes utilizados

  • typescript
  • ts-node-dev
  • supertest
  • rimraf
  • eslint
  • prettier
  • jest
  • dotenv
  • cors
  • express

Note que estou utilizando ESLint no lugar do TSLint. Isso ocorre pois atualmente o ESLint tem suporte pleno as features do TypeScript, sem falar que a própria equipe de desenvolvimento do TSLint tem incentivado essa migração e juntado esforços entorno do ESLint.

🧑‍💻 Mãos a obra

Para os passos abaixo estou considerando que você já inicializou um diretório como projeto Node. Exemplo:

yarn init -y

Esse é um daqueles plugins independente de tecnologia, ou seja, utilizo em todo e qualquer projeto que vou fazer. Veja minha configuração genérica:

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

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

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

yarn add typescript ts-node-dev rimraf @types/node -D
  • typescript: instala o TypeScript e a command line tsc
  • ts-node-dev: servidor web de desenvolvimento para TypeScript
  • rimraf: utilizaremos este modulo como utilitário para excluir builds antigas de forma automática
  • @types/node: definição de tipos para o conjunto nativo de APIs/módulos do NodeJS

Execute a instrução abaixo para gerar o tsconfig.json (arquivo de definições do TypeScript):

yarn tsc --init --outDir "build" --rootDir "src"

A instrução acima gera o arquivo de configurações com duas referências passadas por nós:

  • outDir: diretório onde a build do projeto será gerada
  • rootDir: diretório raiz onde fica nosso código TypeScript

Para manter uma boa sintaxe e formatação é necessário utilizar estes dois camaradas em conjunto. Para isso instale executando:

yarn add eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-simple-import-sort -D
  • eslint: faz o lint do código, ou seja, verifica erros de sintaxe, formatação e outros aspectos
  • prettier: tem foco total na formatação e padronização do código
  • eslint-config-prettier: desativa regras do ESLint que entram em conflito com o Prettier
  • eslint-plugin-prettier: permite rodar o Prettier como uma regra do ESLint
  • eslint-plugin-simple-import-sort: organiza nossos imports em ordem alfabetica

A instrução abaixo irá gerar o arquivo de definições .eslintrc.*:

yarn eslint --init

Reproduza as respostas conforme abaixo:

  1. Vamos configurar o ESLint com foco na sintaxe e detecção de problema:
Image for post
Image for post
Imagem 01: estratégia de uso do ESLint

2. Utilizaremos o padrão de import/export do ES6:

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

3. Não iremos utilizar nenhum dos frameworks listados:

Image for post
Image for post
Imagem 03: framework nativamente suportados

4. Iremos utilizar TypeScript:

Image for post
Image for post
imagem 04: Projeto baseado em TypeScript

5. Nosso código irá rodar do lado do servidor, ou seja, no node:

Image for post
Image for post
Imagem 05: plataforma onde o projeto irá rodar

6. Formato em que o arquivo de configurações do ESLint será gerado. Pessoalmente gosto de escolher JSON:

Image for post
Image for post
Imagem 06: formato do arquivo de configurações

7 — Aqui iremos responder NO a solicitação para instalação das dependências de definição de tipos. Esse caminho é necessário pois estas seriam instaladas com NPM, porém nós estamos utilizando YARN:

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

Agora vamos instalar as duas dependências citadas na imagem 07 como dependências de desenvolvimento:

yarn add @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest -D

Agora edite o arquivo gerado (.eslintrc.json) e substitua seu conteúdo por:

{
"env": {
"es2020": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:prettier/recommended",
"prettier/@typescript-eslint",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": [
"prettier",
"@typescript-eslint",
"simple-import-sort"
],
"ignorePatterns": "build",
"rules": {
"prettier/prettier": "error",
"simple-import-sort/sort": "warn"
}
}

Agora crie um arquivo chamado .prettierrc e adicione este conteúdo a ele:

{
"semi": false,
"singleQuote": true,
"trailingComma": "es5"
}

🔗 Para saber mais, bem como configurar o autoFixOnSave no VSCode, acesse: https://link.medium.com/66cqmzCI18

Instalando dependências:

yarn add jest ts-jest @types/jest -D

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

yarn jest --init

Um questionário será exibido. Responda ao questionário apresentado conforme referências abaixo:

Image for post
Image for post
Imagem 08: steps de configuração de Jest

O passo 3 nos permite trabalhar com cobertura de código, ou seja, conseguimos especificar caminhos a serem observados pelo Jest, este por sua vez irá reportar o quanto do código está coberto por teste e o que exatamente não está coberto. Caso não queira este recurso basta responder n.

Edite o arquivo jest.config.js, localize as variáveis preset e testMatch e as defina com os valores abaixo:

preset: 'ts-jest',
testMatch: ["**/__tests__/**/*.ts"],

Pessoalmente gosto de centralizar todos os testes em um diretório __tests__ na raiz do projeto, além disso, como o projeto é em TypeScript, nada mais justo do que procurar somente arquivos .ts. Quanto mais simples for a regex mais rápido será o processo de busca dos arquivos.

Edite o arquivo .eslintrc.json e adicione a propriedade "jest": true conforme abaixo:

{
"env": {
"es2020": true,
"node": true,
"jest": true
//... código omitido

Caso tenha interesse em utilizar o Coverage, será necessário editar o arquivo jest.config.js, localize e defina as variáveis conforme abaixo:

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

Em collectCoverageFrom você deverá informar um array contendo a localização de todos os arquivos que devem ser cobertos pelo Coverage. Supondo que eu quisesse cobrir outro diretório que está no mesmo nível do services, bastaria adicionar uma condicional regex: <rootDir>/src/(services|helpers)/**/*.ts. Para outros padrões de regex consulte a documentação oficial.

O resultado do Covarege será jogado no diretório definido na variável: coverageDirectory, lembre-se de adicionar esse diretório ao .gitignore.

Crie o arquivo __tests__/sum.ts e adicione este conteúdo a ele:

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

O arquivo acima serve tão somente para validar se o Jest está executando corretamente.

Agora rode a instrução:

yarn test

Como resultado teremos o seguinte output na console:

Image for post
Image for post
Imagem 09: output do Jest
  1. Resultado de cada uma das etapas
  2. Coverage Summary, ou seja, cobertura de testes. Lembra que no arquivo jest.config.js preenchemos a definição collectCoverageFrom? Pois bem, o Coverage Summary calcula a cobertura de teste para os caminhos informados em collectCoverageFrom
  3. Resumo do teste

Que coisa linda não é mesmo!. Porém a cereja do bolo está em ./covarage/lcov-report/index.html, abra esse arquivo no navegador e veja a cobertura dos seus testes.

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

Será necessário editar o tsconfig.json e adicionar tanto o coverage quanto o __tests__ a lista de diretórios ignorados:

"exclude": ["coverage", "**/__tests__"]

A definição acima deve ser inserida no mesmo nível de compilerOptions e não como elemento interno. Se você esquecer de adicionar essa definição ao arquivo o seu processo de build irá falhar.

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",
"dev": "ts-node-dev --respawn --transpile-only --inspect -- ./src/start/server.ts",
"lint": "eslint --fix --ext .js,.ts,.json .",
"test": "jest"
}
  • main: indica o endpoint da nossa aplicação. Observe que estamos apontando para o diretório de build, ou seja, ao executar node . este endpoint é quem será chamado
  • build: utiliza o rimraf para excluir o diretório ./build, na sequência gera os arquivos JavaScript utilizando o tsc
  • dev: sobe o ts-node-dev:
  • => --respawn: ativa o modo watching
  • => --transpile-only: se preocupa somente por executar (transpilar o código para JavaScript), isso garante melhor performance;
  • => --inspect: habilita para conexão de ferramentas de debug;
  • => -- ./src/start/server.ts: ponto de entrada da nossa aplicação
  • lint: permite executar o ESLint por meio do terminal
  • test: dispara o jest

Para executar qualquer um dos scripts criados basta executar:

yarn nome_do_script

Exemplo:

yarn lint

⚠️ Considerando que nosso diretório ./src não tem nenhum arquivo TypeScript ainda, executar qualquer outro script irá resultar em uma saída com erro, então não se assuste pois isso já era esperado.

Vamos as dependências:

yarn add 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:

yarn add @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_USER=
DB_PASS=
DB_NAME=

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

import { Router } from 'express'const router = Router()router.get('/', (req, 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, NextFunction, 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((req: Request, res: Response, next: NextFunction) => {
res.status(404).json({ message: 'Page Not Found' })
})
app.use((err: Errback, req: Request, res: Response, next: NextFunction) => {
return res.status(500).json({ message: 'Internal Error' })
})
export default app

Após salvar o arquivo acima o ESLint irá apresentar um warning de no-unused-vars para as linhas 14 e 18. Para resolver este "problema" edite o arquivo .eslintrc.json e logo abaixo da linha simple-import-sort/sort adicione:

"@typescript-eslint/no-unused-vars": ["warn", { "args": "after-used", "argsIgnorePattern": "^next$" }]

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) || 3000app.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:

yarn dev

Se tudo correu bem teremos como retorno o seguinte output:

Image for post
Image for post
Imagem 10: 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!" }

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:

yarn add 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:

collectCoverageFrom: ['<rootDir>/src/(services|start)/**/*.ts'],

Observe que no código acima estou olhando tanto para o diretório ./src/services quanto para ./src/start. Eu poderia ter incluido como entrada separada no array, porém, já que ambos os diretórios estão no mesmo nível vamos abusar um pouco das regex.

Agora crie o arquivo de teste __tests__/start/app.ts:

import request from 'supertest'import app from '../../src/start/app'describe('Route testing', () => {
it('Should return an http 200 and a "message" property (route: GET /)', async () => {
const res = await request(app).get('/')
expect(res.status).toEqual(200)
expect(res.body).toHaveProperty('message')
})
// A validação do retorno 404 irei deixar como desafio
})

No console teremos o seguinte output:

Image for post
Image for post
Imagem 11: output de teste real

Agora vamos abrir o arquivo: ./covarage/lcov-report/index.html no navegador e ver o resultado do Coverage Report:

Image for post
Image for post
Imagem 12: cobertura de código do Covarage

Lindo né! Na imagem acima clique em app.ts:

Image for post
Image for post
Imagem 13: cobertura de código linha a linha

As linhas em verde indicam por onde os testes passaram, já as linhas vermelhas indicam que nenhum dos nossos testes atingiram aquele trecho do código. Show de bola né!?

“O ideal é tentarmos cobrir com testes a maior fatia possível de código, porém, é arrogância pensar que podemos ter uma cobertura 100% de toda a aplicação…”

Extra (Debug no VSCode)

Nosso servidor ts-node-dev está sendo iniciado com a flag --inspect, ou seja, já está subindo em modo de debug. Para que o VSCode consiga se conectar basta criar-mos o arquivo .vscode/launch.json e adicionar este conteúdo ao mesmo:

{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Debug",
"restart": true,
"protocol": "inspector",
"skipFiles": [
"<node_internals>/**"
]
}
]
}

Antes de executar o próximo passa verifique se o servidor de desenvolvimento está rodando, caso não esteja basta executar:

yarn dev

Considerando que você está no VSCode, siga os passos indicados na tela:

Image for post
Image for post
Imagem 14: VSCode em modo debug
  1. Abri o arquivo ./src/routes.ts e adicionei um breakpoint na linha 6
  2. Abri a tela de debug do VSCode
  3. Iniciei o debug
  4. Quando a barra inferir mudar de cor significa que a ferramenta consegui se conectar ao servidor em modo debug

Agora chame o endereço http://localhost:3000 no navegador, imediatamente você será direcionado para a seguinte tela:

Image for post
Image for post
Imagem 15: Debug em ação
  1. Controles do debug
  2. Variáveis existentes até o momento do breakpoint
  3. Local onde podemos regitrar um variável para ser observada

console.log() ajuda, porém, o modo debug é outro nível né 😉.

Conclusão

Gostou deste material? Caso tenha gostado deixe um comentário que eu me encarregarei de providenciar uma continuação para este material.

Pessoalmente tenho adotado o EditorConfig em todos os meus projetos, seja qual for a tecnologia utilizada. Todos os meus projetos com NodeJS estão utilizando ESLint e Prettier, seja com JavaScript ou TypeScript, não importa.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store