O que é o Jest?
O Jest é um test runner de JavaScript criado no Facebook e, atualmente, uma das ferramentas de teste mais utilizadas no ecossistema. Ele é batteries-included: assertions, mocking, snapshot testing e coverage vêm todos em um único pacote, e um novo projeto precisa de pouquíssima configuração para começar.
Durante anos, o Jest foi a escolha padrão para projetos React e Node.js, e continua dominante em bases de código existentes. Mesmo com a ascensão do Vitest, a API do Jest é o vocabulário compartilhado dos testes em JavaScript, o que torna vantajoso conhecê-lo, independentemente de qual runner você utilize no dia a dia.
Escrevendo testes
Um arquivo de teste do Jest usa describe para agrupar e test (ou it) para declarar um caso.
// cart.test.js
import { Cart } from "./cart";
describe("Cart", () => {
test("starts empty", () => {
expect(new Cart().items).toEqual([]);
});
test("adds an item", () => {
const cart = new Cart();
cart.add({ id: 1, price: 10 });
expect(cart.items).toHaveLength(1);
});
});
Use beforeEach e afterEach para setup e teardown, e beforeAll e afterAll quando um recurso deve ser criado apenas uma vez para todo o arquivo. Nomeie os testes de acordo com o comportamento, para que uma falha indique exatamente o que quebrou.
Matchers
A API de expect do Jest é ampla e expressiva.
// matchers.test.js
expect(value).toBe(5); // strict equality
expect(value).toEqual({ a: 1 }); // deep equality
expect(value).toBeDefined();
expect(list).toContain("a");
expect(fn).toThrow("invalid");
expect(value).toBeGreaterThan(3);
expect(obj).toMatchObject({ id: 1 }); // partial match
O toBe compara com Object.is e é a escolha certa para tipos primitivos. O toEqual compara objetos e arrays recursivamente. O toStrictEqual é mais rigoroso com tipos e propriedades undefined. O toMatchObject é útil quando você se importa apenas com um subconjunto de uma estrutura.
Mocks, spies e mocking de módulos
O mocking do Jest é um de seus recursos mais poderosos.
// users.test.js
import { jest } from "@jest/globals";
import { getUser } from "./users";
jest.mock("./http", () => ({
get: jest.fn().mockResolvedValue({ id: 1, name: "Ada" }),
}));
test("returns a user", async () => {
await expect(getUser(1)).resolves.toEqual({ id: 1, name: "Ada" });
});
jest.fn cria uma função mock, jest.spyOn envolve um método real para que você possa observar as chamadas e restaurá-lo posteriormente, e jest.mock substitui um módulo. Como jest.mock sofre hoisting (é içada) acima dos imports, o mock já está configurado antes do carregamento do módulo sob teste.
Sempre resete os mocks entre os testes. Ative clearMocks, resetMocks ou restoreMocks na configuração, ou chame o helper ...AllMocks correspondente em afterEach. O estado de mocks vazados é um dos motivos mais comuns para testes passarem individualmente, mas falharem quando executados juntos.
Testes assíncronos
Use async/await com resolves e rejects, exatamente como você faria no Vitest.
// async.test.js
test("rejects on failure", async () => {
await expect(loadUser(-1)).rejects.toThrow("Invalid id");
});
O callback done funciona, mas é fácil de usar incorretamente. Prefira usar await. Para timers, jest.useFakeTimers() junto com jest.advanceTimersByTime() torna os testes de debounce e retry determinísticos.
Snapshots
Snapshots serializam um valor e o comparam em execuções futuras.
// config.test.js
test("builds the default config", () => {
expect(createConfig({ debug: true })).toMatchInlineSnapshot();
});
Inline snapshots ficam no próprio arquivo de teste, o que permite revisá-los em um diff. Snapshots externos são convenientes para saídas grandes, mas frequentemente são atualizados sem inspeção, tornando-se apenas um “carimbo de aprovação”. Use snapshots para dados serializáveis estáveis, e não como um substituto para refletir sobre o que realmente importa.
Configuração
O Jest lê a configuração de jest.config.js, de uma chave jest no package.json ou de uma flag da CLI.
// jest.config.js
export default {
testEnvironment: "jsdom",
clearMocks: true,
collectCoverage: true,
coverageThreshold: {
global: { lines: 80, functions: 80 },
},
transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest"],
},
};
testEnvironment seleciona node ou jsdom. clearMocks evita vazamentos (leakage). Os limites de cobertura (coverage thresholds) transformam uma meta em uma regra obrigatória. O transform decide como TypeScript e JSX são compilados, sendo ts-jest, Babel e SWC as opções mais comuns.
Onde o Jest se encaixa
O Jest é a camada de testes unitários e de integração. Para o comportamento de componentes, combine-o com a Testing Library para testar sob a perspectiva do usuário. Para fluxos completos de usuário, utilize uma ferramenta de end-to-end como Playwright ou Cypress. Se você estiver iniciando um novo projeto Vite, o Vitest oferece a mesma API com um pipeline compartilhado e mais rápido.
Melhores práticas
- Teste o comportamento através de APIs públicas, não de internals privados.
- Mantenha apenas uma asserção lógica por teste, sempre que possível.
- Resete e restaure mocks entre os testes.
- Prefira
async/awaitem vez dedone. - Use fake timers para qualquer funcionalidade dependente de tempo.
- Faça snapshot de estruturas pequenas e estáveis, e revise cada atualização.
- Execute os testes em CI a cada alteração, não apenas localmente.
Erros comuns
- Fazer asserções em detalhes de implementação e quebrar testes durante refatorações.
- Atualizar snapshots cegamente.
- Esquecer de limpar mocks e vazar estado entre os testes.
- Usar timers reais, tornando os testes lentos ou instáveis (flaky).
- Testar tudo no nível unitário e deixar passar bugs de integração.
- Ignorar a cobertura (coverage) enquanto assume que os testes cobrem os caminhos importantes.
Próximos passos
O Jest é uma base confiável e um vocabulário compartilhado em todo o ecossistema. Compare-o com o Vitest para projetos modernos com Vite, adicione a Testing Library para componentes e cubra as jornadas do usuário com Playwright ou Cypress. Depois, escreva um teste para um bug que você corrigiu recentemente e deixe que ele proteja seu código contra regressões.