Voltar para o Blog

Vitest em 2026: O Novo Padrão de Testes no JavaScript Moderno

Olá HaWkers, se você ainda está usando Jest em novos projetos, provavelmente está perdendo tempo e performance. Vitest se consolidou como o framework de testes padrão do ecossistema JavaScript moderno em 2026.

Vamos entender por que a mudança aconteceu e como você pode se beneficiar.

Por Que Vitest Venceu

O Contexto da Mudança

// Timeline da mudança de Jest para Vitest

const testingTimeline = {
  2020: 'Jest é padrão indiscutível',
  2022: 'Vitest 0.x surge com Vite',
  2023: 'Vitest 1.0 - pronto para produção',
  2024: {
    vitest: 'Adoção massiva em novos projetos',
    jest: 'Ainda dominante em legado'
  },
  2025: {
    vitest4: 'Vitest 4.0 estável',
    angular: 'Angular 21 adota Vitest como padrão',
    breaking: 'Ponto de virada'
  },
  2026: {
    status: 'Vitest é o novo padrão',
    jest: 'Mantido para legado',
    newProjects: '80%+ usam Vitest'
  }
};

Vantagens do Vitest

// Por que Vitest superou Jest

const vitestAdvantages = {
  // 1. Performance
  performance: {
    speedup: '2-10x mais rápido que Jest',
    why: 'Reutiliza pipeline do Vite',
    watch: 'Watch mode instantâneo',
    benchmark: `
      Projeto médio (500 testes):
      Jest: 45 segundos
      Vitest: 8 segundos
    `
  },

  // 2. Configuração zero com Vite
  zeroConfig: {
    if: 'Você usa Vite',
    then: 'Vitest funciona sem config',
    shared: 'Mesma config de plugins, aliases, etc',
    dx: 'Não precisa manter duas configs'
  },

  // 3. ESM nativo
  esm: {
    jest: 'ESM é add-on, frequentemente quebra',
    vitest: 'ESM é nativo, funciona perfeitamente',
    impact: 'Pode testar código ESM-only sem hacks'
  },

  // 4. TypeScript nativo
  typescript: {
    jest: 'Requer ts-jest ou babel',
    vitest: 'TypeScript funciona out of box',
    types: 'Types incluídos, sem @types/vitest'
  },

  // 5. API compatível com Jest
  compatibility: {
    api: 'describe, it, expect - mesma API',
    migration: 'Maioria dos testes funciona sem mudança',
    mocks: 'vi.mock() similar a jest.mock()'
  },

  // 6. Features modernas
  modernFeatures: {
    browserMode: 'Testa no browser real (Playwright)',
    snapshotInline: 'Snapshots inline no código',
    coverage: 'v8 coverage nativo',
    ui: 'UI interativa built-in'
  }
};

Começando com Vitest

Setup Básico

// Instalação e configuração

// 1. Instalar
// npm install -D vitest

// 2. Adicionar script no package.json
{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run",
    "test:coverage": "vitest run --coverage"
  }
}

// 3. Configuração (opcional - funciona sem config com Vite)
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    // Globals (describe, it, expect) sem import
    globals: true,

    // Ambiente de teste
    environment: 'jsdom', // ou 'node', 'happy-dom'

    // Setup files
    setupFiles: ['./tests/setup.ts'],

    // Coverage
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html'],
      exclude: ['node_modules/', 'tests/']
    },

    // Incluir/excluir arquivos
    include: ['**/*.{test,spec}.{ts,tsx}'],
  },
});

Escrevendo Testes

// Sintaxe básica - muito similar a Jest

// math.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { sum, multiply } from './math';

describe('Math functions', () => {
  it('should sum two numbers', () => {
    expect(sum(1, 2)).toBe(3);
  });

  it('should multiply two numbers', () => {
    expect(multiply(3, 4)).toBe(12);
  });
});

// Com globals: true no config, pode omitir imports
describe('Math functions', () => {
  it('should sum two numbers', () => {
    expect(sum(1, 2)).toBe(3);
  });
});

Mocking

// Mocking em Vitest

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fetchUser } from './api';
import { UserService } from './UserService';

// Mock de módulo inteiro
vi.mock('./api', () => ({
  fetchUser: vi.fn()
}));

describe('UserService', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should fetch user by id', async () => {
    // Configurar mock
    vi.mocked(fetchUser).mockResolvedValue({
      id: 1,
      name: 'John'
    });

    const service = new UserService();
    const user = await service.getUser(1);

    expect(fetchUser).toHaveBeenCalledWith(1);
    expect(user.name).toBe('John');
  });
});

// Mock de função específica
const mockFn = vi.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue(asyncValue);
mockFn.mockImplementation((x) => x * 2);

// Spy em método existente
const spy = vi.spyOn(object, 'method');
spy.mockReturnValue('mocked');

// Mock de timers
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.runAllTimers();
vi.useRealTimers();

Testando Componentes

// Testando componentes React com Vitest + Testing Library

// Button.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('should render with text', () => {
    render(<Button>Click me</Button>);

    expect(screen.getByRole('button')).toHaveTextContent('Click me');
  });

  it('should call onClick when clicked', async () => {
    const handleClick = vi.fn();
    render(<Button onClick={handleClick}>Click</Button>);

    await fireEvent.click(screen.getByRole('button'));

    expect(handleClick).toHaveBeenCalledOnce();
  });

  it('should be disabled when loading', () => {
    render(<Button loading>Submit</Button>);

    expect(screen.getByRole('button')).toBeDisabled();
  });
});

// Setup para Testing Library (tests/setup.ts)
import '@testing-library/jest-dom/vitest';

Testando Vue

// Testando componentes Vue com Vitest

// Counter.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';

describe('Counter', () => {
  it('should render initial count', () => {
    const wrapper = mount(Counter, {
      props: { initialCount: 5 }
    });

    expect(wrapper.text()).toContain('5');
  });

  it('should increment when button clicked', async () => {
    const wrapper = mount(Counter);

    await wrapper.find('button').trigger('click');

    expect(wrapper.text()).toContain('1');
  });
});

Features Avançadas

Browser Mode

// Testar no browser real com Playwright

// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    browser: {
      enabled: true,
      name: 'chromium', // ou 'firefox', 'webkit'
      provider: 'playwright',
    },
  },
});

// Testes rodam no browser real!
// Útil para:
// - Canvas, WebGL
// - Web APIs específicas
// - Visual regression

// visual.test.ts
import { describe, it, expect } from 'vitest';
import { page } from '@vitest/browser/context';

describe('Visual tests', () => {
  it('should render chart correctly', async () => {
    await page.goto('/chart');

    // Screenshot para visual regression
    await expect(page.locator('.chart')).toMatchScreenshot();
  });
});

Inline Snapshots

// Snapshots inline - mais conveniente que arquivos

import { describe, it, expect } from 'vitest';
import { formatUser } from './formatUser';

describe('formatUser', () => {
  it('should format user object', () => {
    const user = { name: 'John', age: 30 };

    // Snapshot inline - atualiza no próprio arquivo!
    expect(formatUser(user)).toMatchInlineSnapshot(`
      {
        "displayName": "John (30 years old)",
        "initials": "J"
      }
    `);
  });
});

// Quando você roda vitest -u, o snapshot é atualizado
// diretamente no arquivo de teste

Type Testing

// Testar tipos TypeScript

// types.test-d.ts (note o -d no nome)
import { describe, it, expectTypeOf } from 'vitest';
import { createStore } from './store';

describe('Store types', () => {
  it('should infer state type correctly', () => {
    const store = createStore({ count: 0 });

    // Testa se o tipo está correto
    expectTypeOf(store.state.count).toBeNumber();
    expectTypeOf(store.state.count).not.toBeString();
  });

  it('should accept correct action types', () => {
    type Action = { type: 'increment' } | { type: 'decrement' };

    expectTypeOf<Action>().toMatchTypeOf<{ type: string }>();
  });
});

// vitest.config.ts
export default defineConfig({
  test: {
    typecheck: {
      enabled: true,
    },
  },
});

UI Mode

// Interface visual para testes

// Rode: npx vitest --ui

// Features da UI:
// - Visualizar todos os testes
// - Filtrar por status (passed, failed, skipped)
// - Ver código do teste
// - Ver output de console
// - Re-run testes específicos
// - Module graph visualization

// Excelente para:
// - Debug de testes falhando
// - Entender cobertura
// - Onboarding de novos devs

Migrando de Jest para Vitest

Guia de Migração

// Passos para migrar

const migrationGuide = {
  // 1. Instalar Vitest
  step1: `
    npm remove jest @types/jest ts-jest babel-jest
    npm install -D vitest
  `,

  // 2. Criar config
  step2: `
    // vitest.config.ts
    import { defineConfig } from 'vitest/config';

    export default defineConfig({
      test: {
        globals: true, // Para compatibilidade com Jest
        environment: 'jsdom',
      },
    });
  `,

  // 3. Atualizar scripts
  step3: `
    // package.json
    {
      "scripts": {
        "test": "vitest",
        "test:run": "vitest run"
      }
    }
  `,

  // 4. Search and replace
  step4: {
    from: "jest.fn()",
    to: "vi.fn()",

    from2: "jest.mock(",
    to2: "vi.mock(",

    from3: "jest.spyOn(",
    to3: "vi.spyOn(",

    from4: "@jest/globals",
    to4: "vitest"
  },

  // 5. Atualizar imports (se não usar globals)
  step5: `
    // Antes
    import { jest } from '@jest/globals';

    // Depois
    import { vi } from 'vitest';
  `
};

Diferenças de API

// Diferenças entre Jest e Vitest

const apiDifferences = {
  // Mocking
  mocking: {
    jest: 'jest.fn(), jest.mock(), jest.spyOn()',
    vitest: 'vi.fn(), vi.mock(), vi.spyOn()',
    note: 'Apenas prefixo diferente'
  },

  // Timers
  timers: {
    jest: 'jest.useFakeTimers()',
    vitest: 'vi.useFakeTimers()',
    extra: 'Vitest tem vi.advanceTimersByTimeAsync()'
  },

  // Module mocking
  moduleMock: {
    jest: `
      jest.mock('./module', () => ({
        foo: jest.fn()
      }));
    `,
    vitest: `
      vi.mock('./module', () => ({
        foo: vi.fn()
      }));
    `,
    vitestAlternative: `
      // Vitest também suporta:
      vi.mock('./module', async (importOriginal) => {
        const actual = await importOriginal();
        return {
          ...actual,
          foo: vi.fn()
        };
      });
    `
  },

  // Snapshot
  snapshot: {
    jest: 'expect(x).toMatchSnapshot()',
    vitest: 'expect(x).toMatchSnapshot()',
    extra: 'Vitest tem toMatchInlineSnapshot()'
  },

  // Globals
  globals: {
    jest: 'Globals por padrão',
    vitest: 'Precisa de globals: true ou imports explícitos',
    recommendation: 'Use imports explícitos para type safety'
  }
};

Codemod Automático

// Use codemod para migração automatizada

// Instalar
// npx @vitest/codemod jest

// O codemod faz:
// - Renomeia jest.* para vi.*
// - Atualiza imports
// - Ajusta configuração

// Depois, revise manualmente:
// - Mocks complexos
// - Setup files
// - Custom matchers

Boas Práticas com Vitest

Organização de Testes

// Estrutura recomendada

const testStructure = {
  // Opção 1: Co-located (recomendado)
  colocated: `
    src/
      components/
        Button/
          Button.tsx
          Button.test.tsx    ← Junto do código
          Button.stories.tsx
      utils/
        format.ts
        format.test.ts       ← Junto do código
  `,

  // Opção 2: Pasta separada
  separate: `
    src/
      components/
        Button.tsx
    tests/
      components/
        Button.test.tsx      ← Pasta separada
  `,

  recommendation: `
    Co-located é melhor:
    - Fácil encontrar testes
    - Refatorar junto
    - Incentiva escrever testes
  `
};

Patterns Úteis

// Patterns que funcionam bem

// 1. Factory functions para fixtures
function createUser(overrides = {}) {
  return {
    id: 1,
    name: 'John',
    email: 'john@example.com',
    ...overrides
  };
}

it('should update user name', () => {
  const user = createUser({ name: 'Jane' });
  // ...
});

// 2. Custom matchers
expect.extend({
  toBeValidEmail(received) {
    const pass = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(received);
    return {
      pass,
      message: () => `expected ${received} to be valid email`
    };
  }
});

// 3. Testing utilities
function renderWithProviders(ui, options = {}) {
  return render(ui, {
    wrapper: ({ children }) => (
      <QueryClientProvider client={queryClient}>
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </QueryClientProvider>
    ),
    ...options
  });
}

// 4. Describe blocks organizados
describe('UserService', () => {
  describe('getUser', () => {
    it('should return user when found', () => {});
    it('should throw when not found', () => {});
  });

  describe('createUser', () => {
    it('should create user with valid data', () => {});
    it('should reject invalid email', () => {});
  });
});

Conclusão

Vitest se tornou o padrão de testes em JavaScript por boas razões: é mais rápido, mais moderno, e tem melhor DX que Jest. A migração é simples e os benefícios são imediatos.

Quando usar Vitest:

  • Novos projetos (sempre)
  • Projetos com Vite (migração fácil)
  • Projetos que precisam de ESM
  • Quando performance de testes importa

Quando manter Jest:

  • Projetos legados funcionando bem
  • Custo de migração não compensa
  • Dependências que requerem Jest

Ações recomendadas:

  1. Use Vitest em novos projetos
  2. Planeje migração de projetos ativos
  3. Explore browser mode para testes visuais
  4. Use UI mode para debug

Para entender mais sobre o ecossistema JavaScript moderno, leia: VoidZero 2026.

Bora pra cima! 🦅

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário