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 testExecuta 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 default
}
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 testesbun test --watch - Modo watch (re-executa ao salvar)bun test calculator.test.ts - Executa arquivo específicobun test --timeout 10000 - Define timeout personalizadobun 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);
});
});