🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
MÓDULO 1 - AULA 3

Test Runner Nativo - Substituindo Jest

Domine o test runner mais rápido do JavaScript, sem configuração

25 minutos
Intermediário
Por que Test Runner Nativo?

O Bun inclui um test runner nativo que elimina a necessidade de Jest, Vitest ou outras ferramentas de teste.

Problemas com Jest

  • • Configuração complexa
  • • Lento para inicializar
  • • Precisa de babel/ts-jest
  • • Muitas dependências
  • • Problemas com ESM

Bun Test Runner

  • • Zero configuração
  • • Extremamente rápido
  • • TypeScript nativo
  • • Built-in no Bun
  • • ESM por padrão
Nosso Primeiro Teste

1. Criar função para testar

// src/utils/calculator.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}

export function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Divisão por zero não é permitida");
  }
  return a / b;
}

export function isEven(num: number): boolean {
  return num % 2 === 0;
}

2. Criar arquivo de teste

// src/utils/calculator.test.ts
import { test, expect, describe } from "bun:test";
import { add, multiply, divide, isEven } from "./calculator";

describe("Calculator", () => {
  test("should add two numbers", () => {
    expect(add(2, 3)).toBe(5);
    expect(add(-1, 1)).toBe(0);
    expect(add(0, 0)).toBe(0);
  });

  test("should multiply two numbers", () => {
    expect(multiply(3, 4)).toBe(12);
    expect(multiply(-2, 3)).toBe(-6);
    expect(multiply(0, 5)).toBe(0);
  });

  test("should divide two numbers", () => {
    expect(divide(10, 2)).toBe(5);
    expect(divide(7, 2)).toBe(3.5);
  });

  test("should throw error when dividing by zero", () => {
    expect(() => divide(5, 0)).toThrow("Divisão por zero não é permitida");
  });

  test("should check if number is even", () => {
    expect(isEven(4)).toBe(true);
    expect(isEven(3)).toBe(false);
    expect(isEven(0)).toBe(true);
  });
});

3. Executar os testes

bun test
Executa todos os arquivos *.test.ts, *.test.js, *.spec.ts, *.spec.js
Matchers Disponíveis

O Bun test runner é compatível com a API do Jest, incluindo todos os matchers principais:

Matchers Básicos

expect(value).toBe(expected)
expect(value).toEqual(expected)
expect(value).toBeNull()
expect(value).toBeUndefined()
expect(value).toBeTruthy()
expect(value).toBeFalsy()
expect(value).toBeGreaterThan(3)
expect(value).toBeCloseTo(0.3)

Matchers Avançados

expect(array).toContain(item)
expect(string).toMatch(/pattern/)
expect(fn).toThrow()
expect(fn).toThrow("message")
expect(object).toHaveProperty("key")
expect(array).toHaveLength(3)
Testes Assíncronos

Função assíncrona para testar

// src/services/api.ts
export async function fetchUser(id: number) {
  // Simula uma chamada de API
  await new Promise(resolve => setTimeout(resolve, 100));
  
  if (id <= 0) {
    throw new Error("ID inválido");
  }
  
  return {
    id,
    name: `User ${id}`,
    email: `user${id}@example.com`
  };
}

export async function fetchUsers(): Promise<any[]> {
  await new Promise(resolve => setTimeout(resolve, 200));
  return [
    { id: 1, name: "João" },
    { id: 2, name: "Maria" }
  ];
}

Testes assíncronos

// src/services/api.test.ts
import { test, expect, describe } from "bun:test";
import { fetchUser, fetchUsers } from "./api";

describe("API Service", () => {
  test("should fetch user by id", async () => {
    const user = await fetchUser(1);
    
    expect(user).toEqual({
      id: 1,
      name: "User 1",
      email: "user1@example.com"
    });
  });

  test("should throw error for invalid id", async () => {
    await expect(fetchUser(-1)).rejects.toThrow("ID inválido");
  });

  test("should fetch all users", async () => {
    const users = await fetchUsers();
    
    expect(users).toHaveLength(2);
    expect(users[0]).toHaveProperty("name", "João");
  });
});
Mocks e Spies

Usando mocks

// src/services/database.test.ts
import { test, expect, mock, spyOn } from "bun:test";

// Mock de uma função
const mockFetch = mock(() => Promise.resolve({ 
  json: () => Promise.resolve({ data: "test" }) 
}));

test("should mock fetch", async () => {
  // @ts-ignore
  global.fetch = mockFetch;
  
  const response = await fetch("/api/test");
  const data = await response.json();
  
  expect(data).toEqual({ data: "test" });
  expect(mockFetch).toHaveBeenCalledTimes(1);
});

// Spy em métodos
test("should spy on console.log", () => {
  const spy = spyOn(console, "log");
  
  console.log("Hello, World!");
  
  expect(spy).toHaveBeenCalledWith("Hello, World!");
  
  spy.mockRestore();
});
⚙️ Configuração Avançada

bunfig.toml (opcional)

# bunfig.toml
[test]
# Timeout padrão para testes (em ms)
timeout = 5000

# Executar testes em paralelo
parallel = true

# Padrão de arquivos de teste
testMatch = ["**/*.test.{js,ts,tsx,jsx}"]

# Setup file (executado antes de cada teste)
setupFiles = ["./test-setup.ts"]

Setup file (test-setup.ts)

// test-setup.ts
import { beforeAll, afterAll } from "bun:test";

// Configuração global antes de todos os testes
beforeAll(() => {
  console.log("🧪 Iniciando testes...");
  
  // Configurar variáveis de ambiente para teste
  process.env.NODE_ENV = "test";
  process.env.DATABASE_URL = "sqlite://test.db";
});

// Limpeza após todos os testes
afterAll(() => {
  console.log("✅ Testes finalizados!");
});
🚀 Comandos de Teste
bun test - Executa todos os testes
bun test --watch - Modo watch (re-executa ao salvar)
bun test calculator.test.ts - Executa arquivo específico
bun test --timeout 10000 - Define timeout personalizado
bun test --bail - Para no primeiro erro
⚡ Performance vs Jest

Jest

  • • Startup: ~2-3 segundos
  • • 100 testes: ~5-8 segundos
  • • Configuração: Complexa
  • • TypeScript: Precisa ts-jest
  • • Dependências: ~50MB

Bun Test

  • • Startup: ~50ms
  • • 100 testes: ~500ms
  • • Configuração: Zero
  • • TypeScript: Nativo
  • • Dependências: 0MB (built-in)
🚀 Bun Test é 10x mais rápido!
🎯 Exercício Prático

Vamos criar testes para funções do nosso projeto Restaurantix:

Desafio:

  • 1. Crie uma função validateEmail(email: string)
  • 2. Crie uma função calculateOrderTotal(items: OrderItem[])
  • 3. Escreva testes para ambas as funções
  • 4. Execute os testes com bun test --watch

Solução:

// src/utils/validation.ts
export function validateEmail(email: string): boolean {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

export interface OrderItem {
  name: string;
  price: number;
  quantity: number;
}

export function calculateOrderTotal(items: OrderItem[]): number {
  return items.reduce((total, item) => {
    return total + (item.price * item.quantity);
  }, 0);
}

// src/utils/validation.test.ts
import { test, expect, describe } from "bun:test";
import { validateEmail, calculateOrderTotal } from "./validation";

describe("Validation Utils", () => {
  test("should validate email correctly", () => {
    expect(validateEmail("test@example.com")).toBe(true);
    expect(validateEmail("invalid-email")).toBe(false);
    expect(validateEmail("")).toBe(false);
  });

  test("should calculate order total", () => {
    const items = [
      { name: "Pizza", price: 25.90, quantity: 2 },
      { name: "Refrigerante", price: 5.50, quantity: 1 }
    ];
    
    expect(calculateOrderTotal(items)).toBe(57.30);
    expect(calculateOrderTotal([])).toBe(0);
  });
});