Testes unitários no Next.js: guia completo com Jest e React Testing Library

Às dez da manhã de uma segunda-feira, o líder técnico mandou uma mensagem no grupo: “A partir desta semana vamos adicionar testes unitários ao projeto. No Next.js, vamos usar Jest.”
Eu já tinha escrito testes para React, mas não fazia ideia de como testar esse conjunto de App Router e Server Components do Next.js. Abri o projeto, executei npm install jest e rodei npm test cheio de confiança. Então a tela se encheu de erros vermelhos:
Error: Cannot use import statement outside a module
SyntaxError: Unexpected token 'export'
Cannot find module 'next/navigation'
Passei dois dias alternando entre arquivos de configuração, Stack Overflow e GitHub Issues. Testei mais de dez configurações e alterei o jest.config.js inúmeras vezes. Quando os testes finalmente funcionaram, quase joguei o teclado para o alto para comemorar.
Configurar o ambiente de testes do Next.js é mais complexo do que parece, mas também não é o mistério que muitos textos na internet fazem parecer. Quando você entende algumas configurações centrais e evita os erros mais comuns, dá para colocar tudo para rodar em cerca de dez minutos. Neste artigo, vamos configurar do zero Next.js 15, Jest e React Testing Library, além de ver como testar Client Components, Server Components, Hooks e Mocks de API.
Configuração do ambiente de testes, do zero
Vamos começar fazendo o ambiente funcionar. Esta parte pode parecer cansativa, mas explicarei por que cada opção é necessária para que você não apenas copie o código sem entendê-lo.
Instale as dependências
Abra o terminal e instale todos estes pacotes de uma vez:
npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node @types/jest
Uma explicação rápida sobre cada pacote:
- jest: o framework de testes
- jest-environment-jsdom: simula um ambiente de navegador, pois componentes React precisam do DOM
- @testing-library/react: ferramenta para testar componentes React
- @testing-library/jest-dom: adiciona asserções como
toBeInTheDocument() - ts-node e @types/jest: suporte a TypeScript; se você usa JavaScript, pode ignorá-los
Depois da instalação, não execute os testes ainda: os arquivos de configuração ainda não foram criados.
Crie o jest.config.ts
Este é o arquivo de configuração central. Crie jest.config.ts na raiz do projeto; se você usa JavaScript, use a extensão .js:
import type { Config } from 'jest'
import nextJest from 'next/jest'
// Esta função carrega automaticamente a configuração do Next.js
const createJestConfig = nextJest({
dir: './', // Raiz do projeto Next.js
})
const config: Config = {
coverageProvider: 'v8', // Ferramenta de cobertura de código
testEnvironment: 'jsdom', // Simula o ambiente do navegador
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], // Configuração executada antes dos testes
}
// Envolve a configuração com createJestConfig e trata as transformações do Next.js
export default createJestConfig(config)
Aqui está o ponto principal: por que envolver a configuração com next/jest?
O next/jest faz automaticamente o seguinte:
- Trata arquivos
.csse.module.css, fazendo Mock para evitar erros nos testes - Trata imagens, fontes e outros recursos estáticos
- Carrega variáveis de ambiente de
.env - Transforma TypeScript e JSX
- Exclui os diretórios
node_modulese.next
Sem ele, você precisaria configurar tudo isso manualmente. Acredite: seria bem trabalhoso.
Crie o jest.setup.ts
Crie também um arquivo jest.setup.ts na raiz. O conteúdo é simples:
import '@testing-library/jest-dom'
Essa linha importa os matchers personalizados do Jest DOM e permite usar asserções como:
expect(element).toBeInTheDocument()expect(element).toHaveClass('active')expect(element).toBeVisible()
Sem esse arquivo, esses métodos não serão reconhecidos.
Configure aliases de caminho, se você usa @/
Se o projeto usa aliases, como import Button from '@/components/Button', é preciso ensinar o Jest a resolver esses caminhos.
Primeiro, veja se o tsconfig.json ou jsconfig.json tem uma configuração como esta:
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/components/*": ["components/*"],
"@/lib/*": ["lib/*"]
}
}
}
Depois, adicione moduleNameMapper ao jest.config.ts:
const config: Config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
// Adicione esta parte
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/lib/(.*)$': '<rootDir>/lib/$1',
},
}
Por que isso é necessário? Por padrão, o Jest não reconhece caminhos como @/; ele entende apenas caminhos relativos ou absolutos. Você precisa dizer: “Quando encontrar @/components/Button, procure em <rootDir>/components/Button.”
Adicione os scripts de teste
Por fim, adicione dois scripts ao package.json:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch"
}
}
npm test: executa todos os testes uma veznpm test:watch: observa os arquivos e repete os testes quando eles mudam
Valide a configuração
Crie __tests__/example.test.ts com um teste simples para validar a configuração:
describe('Example Test', () => {
it('should pass', () => {
expect(1 + 1).toBe(2)
})
})
Execute npm test. Se você vir PASS em verde e 1 passed, a configuração está pronta.
Se aparecer um erro, não entre em pânico. Confira primeiro a seção de problemas comuns mais adiante; 90% dos erros têm uma solução lá.
Testes de componentes na prática
Com a configuração pronta, chegou a hora de escrever testes reais. Os testes de componentes são centrais no Next.js, mas Client Components e Server Components exigem abordagens bem diferentes.
Testes de Client Components: o fluxo padrão
Client Components são os componentes conhecidos que contêm 'use client'. Testá-los é direto.
Imagine o componente de formulário de login LoginForm.tsx:
'use client'
import { useState } from 'react'
export default function LoginForm() {
const [email, setEmail] = useState('')
const [error, setError] = useState('')
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!email.includes('@')) {
setError('Digite um e-mail válido')
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="E-mail"
/>
{error && <span role="alert">{error}</span>}
<button type="submit">Entrar</button>
</form>
)
}
O arquivo de teste LoginForm.test.tsx:
import { render, screen, fireEvent } from '@testing-library/react'
import LoginForm from '@/components/LoginForm'
describe('LoginForm', () => {
it('should render input and button', () => {
render(<LoginForm />)
// Verifica se o campo existe
const emailInput = screen.getByPlaceholderText('E-mail')
expect(emailInput).toBeInTheDocument()
// Verifica se o botão existe
const submitButton = screen.getByRole('button', { name: 'Entrar' })
expect(submitButton).toBeInTheDocument()
})
it('should show error for invalid email', () => {
render(<LoginForm />)
const emailInput = screen.getByPlaceholderText('E-mail')
const submitButton = screen.getByRole('button', { name: 'Entrar' })
// Simula a entrada do usuário
fireEvent.change(emailInput, { target: { value: 'invalid-email' } })
fireEvent.click(submitButton)
// Verifica a mensagem de erro
const errorMessage = screen.getByRole('alert')
expect(errorMessage).toHaveTextContent('Digite um e-mail válido')
})
})
O raciocínio do teste é:
- Renderizar o componente com
render() - Encontrar elementos com
screen.getByXxx(), por função, texto ou placeholder - Simular ações do usuário com
fireEvent - Verificar o resultado com
expect()
Uma dica: prefira getByRole a getByTestId. Uma função como button ou alert está mais próxima do que o usuário realmente percebe, então o teste tende a ser mais estável.
Testes de Server Components: uma limitação incômoda
Server Components são um recurso central do Next.js 15, mas o suporte do Jest ainda não é muito amigável.
O problema principal é que o Jest não oferece suporte a Server Components async.
Por exemplo, considere um componente que obtém dados de um banco:
// app/posts/page.tsx (Server Component)
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
return res.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Se você testar esse componente diretamente, o Jest gera Objects are not valid as a React child.
O que fazer?
Há três abordagens.
Opção 1: extraia a lógica de negócio e teste uma função pura
// lib/posts.ts
export async function getPosts() {
const res = await fetch('https://api.example.com/posts')
if (!res.ok) throw new Error('Failed to fetch')
return res.json()
}
// lib/posts.test.ts
import { getPosts } from './posts'
describe('getPosts', () => {
it('should fetch posts successfully', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([{ id: 1, title: 'Test Post' }]),
})
) as jest.Mock
const posts = await getPosts()
expect(posts).toHaveLength(1)
expect(posts[0].title).toBe('Test Post')
})
})
Assim, você testa a lógica de obtenção de dados, e não o componente. Em outras palavras, extraia a lógica assíncrona complexa e deixe apenas a renderização simples no componente.
Opção 2: teste Server Components síncronos
Se o Server Component não é assíncrono, você pode testá-lo normalmente:
// components/Title.tsx (Server Component, sem async)
export default function Title({ text }: { text: string }) {
return <h1 className="title">{text}</h1>
}
// components/Title.test.tsx
import { render, screen } from '@testing-library/react'
import Title from './Title'
describe('Title', () => {
it('should render title', () => {
render(<Title text="Hello World" />)
const heading = screen.getByRole('heading', { level: 1 })
expect(heading).toHaveTextContent('Hello World')
})
})
Opção 3: complemente com testes E2E
Para Server Components complexos, testes E2E com Playwright ou Cypress são mais confiáveis. Deixe o Jest cuidar da lógica frontend e os testes E2E do fluxo completo; cada ferramenta faz o que sabe fazer melhor.
Minha abordagem é extrair a lógica de negócio central para funções puras, manter os Server Components focados em renderização simples e cobrir o restante com testes E2E.
Técnicas para testar interações
Ao testar interações do usuário, @testing-library/react oferece vários métodos:
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
// Clique
fireEvent.click(button)
// Entrada de texto
fireEvent.change(input, { target: { value: 'test' } })
// Aguarda uma atualização assíncrona
await waitFor(() => {
expect(screen.getByText('Success')).toBeInTheDocument()
})
// Verifica se o elemento está visível
expect(element).toBeVisible()
// Verifica a classe CSS
expect(element).toHaveClass('active')
Veja um exemplo completo de teste de interação assíncrona:
it('should submit form successfully', async () => {
// Mock da API
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ success: true }),
})
) as jest.Mock
render(<LoginForm />)
const emailInput = screen.getByPlaceholderText('E-mail')
const submitButton = screen.getByRole('button', { name: 'Entrar' })
fireEvent.change(emailInput, { target: { value: '[email protected]' } })
fireEvent.click(submitButton)
// Aguarda a mensagem de sucesso aparecer
await waitFor(() => {
expect(screen.getByText('Login realizado com sucesso')).toBeInTheDocument()
})
})
Observe o waitFor: ele aguarda a operação assíncrona antes de fazer a verificação. Se o componente usa useEffect ou atualiza o estado de forma assíncrona, use esse método; caso contrário, a asserção pode ser executada antes da atualização e gerar um falso erro.
Estratégias para testar Hooks
Hooks personalizados são uma das partes mais úteis do React. É possível testá-los dentro de componentes, mas isso mistura a lógica do Hook com a do componente. Uma opção melhor é usar renderHook.
Teste um Hook simples
Imagine um Hook de contador:
// hooks/useCounter.ts
import { useState } from 'react'
export function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue)
const increment = () => setCount(c => c + 1)
const decrement = () => setCount(c => c - 1)
const reset = () => setCount(initialValue)
return { count, increment, decrement, reset }
}
O teste fica assim:
// hooks/useCounter.test.ts
import { renderHook, act } from '@testing-library/react'
import { useCounter } from './useCounter'
describe('useCounter', () => {
it('should initialize with default value', () => {
const { result } = renderHook(() => useCounter())
expect(result.current.count).toBe(0)
})
it('should initialize with custom value', () => {
const { result } = renderHook(() => useCounter(10))
expect(result.current.count).toBe(10)
})
it('should increment count', () => {
const { result } = renderHook(() => useCounter())
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
it('should reset count', () => {
const { result } = renderHook(() => useCounter(5))
act(() => {
result.current.increment()
result.current.increment()
})
expect(result.current.count).toBe(7)
act(() => {
result.current.reset()
})
expect(result.current.count).toBe(5)
})
})
Pontos principais:
renderHookrenderiza o Hookactenvolve operações que atualizam o estado, garantindo que a atualização termine antes da asserçãoresult.currentacessa o valor retornado pelo Hook
Teste um Hook que depende de Context
Se o Hook depende de Context, como um Auth Context, você precisa fornecer o Provider.
// hooks/useAuth.ts
import { useContext } from 'react'
import { AuthContext } from '@/contexts/AuthContext'
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}
No teste, forneça um Provider simulado:
// hooks/useAuth.test.tsx
import { renderHook } from '@testing-library/react'
import { useAuth } from './useAuth'
import { AuthContext } from '@/contexts/AuthContext'
describe('useAuth', () => {
it('should return auth context value', () => {
const mockAuthValue = {
user: { id: 1, name: 'Test User' },
login: jest.fn(),
logout: jest.fn(),
}
const wrapper = ({ children }: { children: React.ReactNode }) => (
<AuthContext.Provider value={mockAuthValue}>
{children}
</AuthContext.Provider>
)
const { result } = renderHook(() => useAuth(), { wrapper })
expect(result.current.user).toEqual({ id: 1, name: 'Test User' })
expect(result.current.login).toBeDefined()
})
it('should throw error when used outside provider', () => {
// Captura o erro
const { result } = renderHook(() => useAuth())
expect(result.error).toEqual(
Error('useAuth must be used within AuthProvider')
)
})
})
A técnica é usar o parâmetro wrapper para envolver o Provider, permitindo que o Hook acesse o Context.
Teste um Hook assíncrono de obtenção de dados
Muitos Hooks atuais obtêm dados. Por exemplo:
// hooks/useFetch.ts
import { useState, useEffect } from 'react'
export function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true)
const response = await fetch(url)
if (!response.ok) throw new Error('Network error')
const json = await response.json()
setData(json)
} catch (err) {
setError(err as Error)
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
return { data, loading, error }
}
Para testar um Hook assíncrono, faça Mock de fetch e aguarde a atualização de estado:
// hooks/useFetch.test.ts
import { renderHook, waitFor } from '@testing-library/react'
import { useFetch } from './useFetch'
describe('useFetch', () => {
beforeEach(() => {
// Redefine o Mock de fetch antes de cada teste
jest.resetAllMocks()
})
it('should fetch data successfully', async () => {
const mockData = { id: 1, title: 'Test' }
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve(mockData),
})
) as jest.Mock
const { result } = renderHook(() => useFetch('/api/data'))
// Estado inicial: loading = true
expect(result.current.loading).toBe(true)
expect(result.current.data).toBeNull()
// Aguarda o carregamento dos dados
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
expect(result.current.data).toEqual(mockData)
expect(result.current.error).toBeNull()
})
it('should handle fetch error', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: false,
})
) as jest.Mock
const { result } = renderHook(() => useFetch('/api/data'))
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
expect(result.current.error).toBeTruthy()
expect(result.current.error?.message).toBe('Network error')
expect(result.current.data).toBeNull()
})
})
Pontos de atenção:
- Use
waitForpara aguardar a operação assíncrona - Redefina os Mocks em
beforeEachpara evitar que um teste afete o outro - Teste tanto o caso de sucesso quanto o de falha
O princípio dos testes de Hooks é simples: simule as dependências, provoque a mudança de estado e verifique o resultado. Com essas três etapas, você consegue testar qualquer Hook.
Guia de técnicas de Mock
Mock é a base dos testes. Sem ele, os testes dependem de APIs, bancos de dados e serviços de terceiros reais, ficando lentos e instáveis. O Next.js tem alguns recursos especiais que precisam de Mock; esta seção mostra como tratá-los.
Mock das rotas do Next.js
Os Hooks de rota do Next.js, como useRouter, usePathname e useSearchParams, não estão disponíveis por padrão no ambiente de teste e precisam de Mock.
Mock de useRouter no App Router:
// __mocks__/next/navigation.ts
export const useRouter = jest.fn()
export const usePathname = jest.fn()
export const useSearchParams = jest.fn()
No arquivo de teste:
import { useRouter } from 'next/navigation'
// Simula o comportamento das rotas
jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
usePathname: jest.fn(),
useSearchParams: jest.fn(),
}))
describe('NavigationComponent', () => {
it('should navigate to home on button click', () => {
const pushMock = jest.fn()
;(useRouter as jest.Mock).mockReturnValue({
push: pushMock,
back: jest.fn(),
forward: jest.fn(),
})
render(<NavigationComponent />)
const button = screen.getByRole('button', { name: 'Voltar ao início' })
fireEvent.click(button)
expect(pushMock).toHaveBeenCalledWith('/')
})
})
Mock de usePathname, que obtém o caminho atual:
import { usePathname } from 'next/navigation'
jest.mock('next/navigation', () => ({
usePathname: jest.fn(),
}))
describe('HeaderComponent', () => {
it('should highlight active nav item', () => {
;(usePathname as jest.Mock).mockReturnValue('/about')
render(<Header />)
const aboutLink = screen.getByRole('link', { name: 'Sobre' })
expect(aboutLink).toHaveClass('active')
})
})
Mock do componente Image do Next.js
next/image também pode causar erro no ambiente de teste porque depende do serviço de otimização de imagens do Next.js.
Opção 1: substitua por uma tag img comum:
// __mocks__/next/image.tsx
const Image = ({ src, alt }: { src: string; alt: string }) => {
return <img src={src} alt={alt} />
}
export default Image
Opção 2: configure um Mock global no jest.config.ts:
const config: Config = {
// ... Outras configurações
moduleNameMapper: {
'^next/image$': '<rootDir>/__mocks__/next/image.tsx',
},
}
Assim, todos os usos de next/image são substituídos automaticamente pela versão simulada.
Mock de requisições de API: três métodos
Método 1: Mock global de fetch, o mais simples:
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ data: 'mock data' }),
})
) as jest.Mock
Método 2: MSW, ou Mock Service Worker, uma opção mais poderosa:
npm install -D msw
// mocks/handlers.ts
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/posts', () => {
return HttpResponse.json([
{ id: 1, title: 'Test Post' },
])
}),
http.post('/api/login', async ({ request }) => {
const { email } = await request.json()
if (email === '[email protected]') {
return HttpResponse.json({ success: true })
}
return HttpResponse.json({ error: 'Invalid email' }, { status: 400 })
}),
]
// mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
Inicie o Mock Server em jest.setup.ts:
import '@testing-library/jest-dom'
import { server } from './mocks/server'
// Inicia o Mock Server antes dos testes
beforeAll(() => server.listen())
// Redefine os handlers depois de cada teste
afterEach(() => server.resetHandlers())
// Encerra o Server depois de todos os testes
afterAll(() => server.close())
A vantagem do MSW é interceptar todas as requisições de rede sem exigir global.fetch em cada teste.
Método 3: Mock de axios, se o projeto usa axios:
npm install -D axios-mock-adapter
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
const mock = new MockAdapter(axios)
describe('API Test', () => {
afterEach(() => {
mock.reset()
})
it('should fetch posts', async () => {
mock.onGet('/api/posts').reply(200, [{ id: 1, title: 'Test' }])
const response = await axios.get('/api/posts')
expect(response.data).toHaveLength(1)
})
})
Mock de variáveis de ambiente
Variáveis de ambiente do Next.js também precisam de Mock nos testes.
Método 1: defina process.env diretamente:
describe('Config Test', () => {
const originalEnv = process.env
beforeEach(() => {
jest.resetModules()
process.env = { ...originalEnv }
})
afterEach(() => {
process.env = originalEnv
})
it('should use API URL from env', () => {
process.env.NEXT_PUBLIC_API_URL = 'https://test-api.com'
const { getApiUrl } = require('@/lib/config')
expect(getApiUrl()).toBe('https://test-api.com')
})
})
Método 2: use um arquivo .env.test:
Crie .env.test; o next/jest o carrega automaticamente:
NEXT_PUBLIC_API_URL=https://test-api.com
DATABASE_URL=postgresql://test:test@localhost:5432/test
Mock de módulos de terceiros: exemplo com Prisma
Se você usa Prisma para consultar o banco de dados, não vai querer se conectar ao banco real durante os testes.
Mock do Prisma Client:
// __mocks__/prisma.ts
export const prisma = {
user: {
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
post: {
findMany: jest.fn(),
create: jest.fn(),
},
}
Use-o no teste:
import { prisma } from '@/lib/prisma'
jest.mock('@/lib/prisma', () => ({
prisma: {
user: {
findUnique: jest.fn(),
},
},
}))
describe('getUserById', () => {
it('should return user', async () => {
const mockUser = { id: 1, name: 'Test User' }
;(prisma.user.findUnique as jest.Mock).mockResolvedValue(mockUser)
const result = await getUserById(1)
expect(result).toEqual(mockUser)
expect(prisma.user.findUnique).toHaveBeenCalledWith({
where: { id: 1 },
})
})
})
Erros comuns de Mock e suas soluções
Problema 1: Cannot find module 'next/router'
Causa: o módulo de rotas do Next.js não recebeu Mock.
Solução: adicione jest.mock('next/navigation') no início do arquivo de teste.
Problema 2: o Mock não funciona.
Causa: jest.mock está no lugar errado. Ele precisa ficar no topo do arquivo, depois dos imports.
import { useRouter } from 'next/navigation'
// O Mock precisa ficar aqui
jest.mock('next/navigation')
describe('Test', () => {
// ...
})
Problema 3: as variáveis de ambiente não são lidas.
Causa: o teste não carregou o arquivo .env.
Solução: confirme que jest.config.ts está envolvido por next/jest, que carrega as variáveis automaticamente.
Com essas técnicas de Mock, você consegue testar praticamente todos os recursos especiais do Next.js.
Solução de problemas comuns
Erros são normais durante a configuração do Jest. A boa notícia é que 90% deles pertencem a poucas categorias e têm soluções conhecidas.
Erro 1: Cannot use import statement outside a module
Mensagem completa:
SyntaxError: Cannot use import statement outside a module
Causa: o Jest não oferece suporte a ES Modules por padrão, enquanto seu código ou uma dependência usa import/export.
Solução: adicione ao jest.config.ts:
const config: Config = {
// ... Outras configurações
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transformIgnorePatterns: [
'node_modules/(?!(module-that-uses-esm)/)',
],
}
Se o problema estiver em um pacote npm, como nanoid ou uuid, adicione o nome à lista de exceções de transformIgnorePatterns:
transformIgnorePatterns: [
'node_modules/(?!(nanoid|uuid)/)',
]
Erro 2: Unexpected token ‘export’
Causa: um arquivo não foi transformado pelo Jest.
Solução: confira transform e transformIgnorePatterns em jest.config.ts. Garanta que next/jest envolva a configuração:
import nextJest from 'next/jest'
const createJestConfig = nextJest({ dir: './' })
export default createJestConfig(config)
next/jest trata automaticamente a transformação de TypeScript e JSX.
Erro 3: Cannot find module ’@/components/…’
Causa: o Jest não reconhece o alias de caminho @/.
Solução: configure moduleNameMapper em jest.config.ts para indicar o destino de @/:
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/lib/(.*)$': '<rootDir>/lib/$1',
'^@/(.*)$': '<rootDir>/$1',
}
Esses caminhos precisam coincidir com a configuração paths de tsconfig.json.
Erro 4: Objects are not valid as a React child
Causa: você tentou testar um Server Component async, que não é suportado pelo Jest.
Solução:
- Extraia a lógica assíncrona para uma função pura e teste-a separadamente
- Ou use testes E2E com Playwright ou Cypress
Não force o Jest a testar Server Components: nenhuma ferramenta serve para tudo.
Erro 5: aviso de act(…)
Mensagem completa:
Warning: An update to Component inside a test was not wrapped in act(...).
Causa: o componente tem uma atualização assíncrona de estado, como useEffect ou setTimeout, mas o teste não esperou a atualização terminar.
Solução: use waitFor ou envolva a operação assíncrona com act:
import { waitFor } from '@testing-library/react'
it('should update state', async () => {
render(<Component />)
await waitFor(() => {
expect(screen.getByText('Updated')).toBeInTheDocument()
})
})
Ou use act:
import { act } from '@testing-library/react'
it('should trigger callback', async () => {
await act(async () => {
render(<Component />)
})
})
Testes lentos? Tente estas otimizações
- Execute os testes em paralelo:
{
"scripts": {
"test": "jest --maxWorkers=4"
}
}
- Teste apenas os arquivos alterados:
npm test -- --onlyChanged
- Desative a cobertura de código durante a depuração:
{
"scripts": {
"test:fast": "jest --no-coverage"
}
}
- Ignore testes lentos com
test.skip:
describe.skip('Slow Tests', () => {
// Estes testes serão ignorados
})
Lista de verificação para diagnóstico
Quando surgir um erro, confira nesta ordem:
- ✅
jest.config.tsestá envolvido pornext/jest? - ✅
jest.setup.tsimporta corretamente@testing-library/jest-dom? - ✅ Os aliases de caminho coincidem com
tsconfig.json? - ✅ Os módulos que precisam de Mock, como
next/navigationenext/image, foram simulados? - ✅ Operações assíncronas usam
waitForouact? - ✅ As versões das dependências são compatíveis, especialmente React 19 e Jest?
Seguindo essa lista, você resolve a maioria dos problemas.
Conclusão
Configurar o ambiente de testes dá trabalho, mas, depois que ele começa a funcionar, a qualidade do código realmente melhora.
Quando comecei a escrever testes, também achei que era perda de tempo: implementar uma funcionalidade levava cinco minutos e escrever o teste, dez. Depois percebi que os testes dão muito mais segurança para refatorar e corrigir bugs. Você altera o código, executa a suíte e, se tudo fica verde, sabe que está no caminho certo; se algo fica vermelho, já sabe o que quebrou.
Minhas recomendações são:
- Não espere o projeto ficar grande para começar. A partir de agora, adicione um teste a cada nova funcionalidade e transforme isso em hábito.
- Não busque 100% de cobertura. Priorize a lógica de negócio central e as partes mais propensas a bugs.
- Se não der para testar Server Components, não force: extraia a lógica ou complemente com testes E2E.
- Quando aparecer um erro, siga a lista de diagnóstico desta seção; muito provavelmente ela levará à solução.
Guarde os arquivos de configuração deste artigo. No próximo projeto, basta copiá-los para colocar o ambiente de testes no ar em dez minutos. Conforme os testes entram na rotina, os bugs diminuem e a qualidade do código melhora naturalmente.
Se você encontrar algum problema ao configurar ou escrever seus testes, deixe um comentário. Eu também passei por esses obstáculos e ficarei feliz em ajudar no que puder.
Processo completo para configurar Jest no Next.js
Etapas detalhadas para configurar do zero um ambiente de testes com Next.js 15, Jest e React Testing Library
⏱️ Estimated time: 15 min
- 1
Step 1: Instale as dependências de teste
Instale o conjunto de pacotes do Jest e da React Testing Library:
• npm install -D jest jest-environment-jsdom
• npm install -D @testing-library/react @testing-library/dom @testing-library/jest-dom
• npm install -D ts-node @types/jest (necessário em projetos TypeScript)
Função de cada pacote:
• jest: núcleo do framework de testes
• jest-environment-jsdom: simula o ambiente DOM do navegador
• @testing-library/react: ferramenta para testar componentes React
• @testing-library/jest-dom: adiciona asserções como toBeInTheDocument
Depois de instalar tudo, não execute os testes ainda; primeiro crie os arquivos de configuração. - 2
Step 2: Crie o arquivo de configuração do Jest
Crie jest.config.ts na raiz do projeto e use next/jest para tratar os recursos do Next.js automaticamente:
```typescript
import type { Config } from 'jest'
import nextJest from 'next/jest'
const createJestConfig = nextJest({ dir: './' })
const config: Config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
// Se você usa aliases de caminho, adicione moduleNameMapper
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/lib/(.*)$': '<rootDir>/lib/$1',
},
}
export default createJestConfig(config)
```
O next/jest trata automaticamente Mocks de CSS e imagens, carregamento de variáveis de ambiente, transformação de TypeScript e exclusão de node_modules. - 3
Step 3: Crie a configuração de inicialização do Jest
Crie jest.setup.ts na raiz do projeto e importe as extensões do Jest DOM:
```typescript
import '@testing-library/jest-dom'
```
Essa linha permite usar asserções estendidas:
• expect(element).toBeInTheDocument()
• expect(element).toHaveClass('active')
• expect(element).toBeVisible()
• expect(element).toHaveTextContent('text')
Sem importar esse arquivo, esses métodos causam o erro 'not a function'. - 4
Step 4: Configure os scripts de teste
Adicione os comandos de teste ao package.json:
```json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
```
Finalidade dos três comandos:
• npm test: executa todos os testes (para CI/CD)
• npm run test:watch: modo de observação, que testa automaticamente após alterações em arquivos (para desenvolvimento)
• npm run test:coverage: gera o relatório de cobertura de código - 5
Step 5: Crie um teste de exemplo para validar a configuração
Crie __tests__/example.test.ts para verificar se o ambiente foi configurado corretamente:
```typescript
describe('Example Test', () => {
it('should pass basic assertion', () => {
expect(1 + 1).toBe(2)
})
})
```
Execute npm test. Se aparecer PASS em verde, a configuração está correta.
Se houver erro, verifique nesta ordem:
1. Se jest.config.ts está envolvido por createJestConfig
2. Se jest.setup.ts contém a importação correta
3. Se os scripts de teste do package.json estão corretos
4. Se os aliases de caminho coincidem com o tsconfig.json
FAQ
Por que é necessário envolver a configuração com next/jest?
• Faz Mock de CSS e arquivos de imagem
• Carrega variáveis de ambiente de .env
• Transforma TypeScript e JSX
• Exclui node_modules e o diretório .next
• Configura as regras de transformação do Next.js Compiler
Sem next/jest, você precisaria configurar tudo isso manualmente, o que é trabalhoso e propenso a erros. A recomendação oficial é envolver a configuração com createJestConfig.
É possível testar Server Components com Jest?
• Server Components síncronos: podem ser testados normalmente
• Server Components async: o Jest não oferece suporte e gera o erro 'Objects are not valid as a React child'
Abordagem recomendada:
1. Extraia a lógica assíncrona para funções puras e teste a obtenção de dados separadamente
2. Deixe os Server Components apenas com renderização simples, sem lógica complexa
3. Use Playwright ou Cypress para testes E2E do fluxo completo
Não tente forçar o Jest a testar todos os Server Components; escolher a ferramenta certa é mais importante.
Como fazer Mock de useRouter do Next.js nos testes?
```typescript
import { useRouter } from 'next/navigation'
jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
usePathname: jest.fn(),
useSearchParams: jest.fn(),
}))
// Defina o valor retornado no teste
const pushMock = jest.fn()
;(useRouter as jest.Mock).mockReturnValue({
push: pushMock,
back: jest.fn(),
forward: jest.fn(),
})
```
O jest.mock deve ficar no topo do arquivo de teste, depois dos imports, e não dentro de describe ou it.
Por que aparece o erro 'Cannot use import statement outside a module'?
Opção 1: configure transformIgnorePatterns (recomendado)
```typescript
// jest.config.ts
const config: Config = {
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transformIgnorePatterns: [
'node_modules/(?!(nanoid|uuid)/)', // Liste os pacotes que usam ESM
],
}
```
Opção 2: confirme que next/jest envolve corretamente a configuração
```typescript
import nextJest from 'next/jest'
const createJestConfig = nextJest({ dir: './' })
export default createJestConfig(config)
```
Em 90% dos casos, algum pacote npm usa ESM; basta adicionar o nome do pacote à lista de exceções de transformIgnorePatterns.
Como fazer Mock de requisições de API? Qual método é recomendado?
Método 1: Mock global de fetch (o mais simples, indicado para projetos pequenos)
```typescript
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ data: 'test' }),
})
) as jest.Mock
```
Método 2: MSW (Mock Service Worker, recomendado para projetos médios e grandes)
• Intercepta todas as requisições de rede
• Aceita lógicas complexas de requisição e resposta
• Evita repetir o Mock em cada teste
• Instalação: npm install -D msw
Método 3: axios-mock-adapter (se o projeto usa axios)
• Biblioteca de Mock específica para axios
• API simples e fácil de usar
A recomendação é usar MSW porque ele se aproxima mais das requisições reais, é reutilizável e funciona bem em equipe.
Como otimizar testes que estão muito lentos?
1. Execute os testes em paralelo (mais eficaz)
```json
{ "scripts": { "test": "jest --maxWorkers=4" } }
```
2. Teste apenas os arquivos alterados
```bash
npm test -- --onlyChanged
```
3. Desative a cobertura de código durante a depuração
```json
{ "scripts": { "test:fast": "jest --no-coverage" } }
```
4. Ignore testes lentos
```typescript
describe.skip('Slow E2E Tests', () => {
// Estes testes serão ignorados
})
```
Durante o desenvolvimento, use --onlyChanged e --no-coverage; no CI/CD, execute a suíte completa.
O que fazer quando o teste ainda não reconhece o alias @/?
tsconfig.json (ou jsconfig.json):
```json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/components/*": ["components/*"],
"@/lib/*": ["lib/*"]
}
}
}
```
jest.config.ts:
```typescript
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/lib/(.*)$': '<rootDir>/lib/$1',
}
```
Observe três pontos:
1. tsconfig usa caminhos relativos, sem <rootDir>
2. jest.config usa caminhos absolutos, com <rootDir>
3. O formato do curinga deve coincidir (.*)
Reinicie os testes depois da alteração para que o alias seja reconhecido.
19 min de leitura · Publicado em: 7 jan 2026 · Atualizado em: 4 set 2026
Guia completo Next.js
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Error Boundary no Next.js: 5 práticas para lidar com erros em runtime
Aprenda a usar error.tsx, global-error.tsx e reset() no Next.js, tratar erros em Server Components e criar uma recuperação segura sem deixar a página em branco.
Parte 19 de 26
Próximo
Testes E2E no Next.js com Playwright: guia prático de automação
Aprenda a automatizar testes E2E em projetos Next.js com Playwright, Page Object Model, testes de API e integração com CI/CD, incluindo armadilhas e configurações usadas em projetos reais.
Parte 21 de 26



Comentários
Entre com GitHub para comentar