Defina testes com uma API semelhante ao Jest importada do módulo integrado bun:test. A longo prazo, o Bun visa compatibilidade completa com Jest; no momento, um conjunto limitado de matchers expect é suportado.
Uso Básico
Para definir um teste simples:
import { expect, test } from "bun:test";
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});Agrupando Testes
Testes podem ser agrupados em suítes com describe.
import { expect, test, describe } from "bun:test";
describe("aritmética", () => {
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});
test("2 * 2", () => {
expect(2 * 2).toBe(4);
});
});Testes Assíncronos
Testes podem ser assíncronos.
import { expect, test } from "bun:test";
test("2 * 2", async () => {
const result = await Promise.resolve(2 * 2);
expect(result).toEqual(4);
});Alternativamente, use o callback done para sinalizar conclusão. Se você incluir o callback done como parâmetro na definição do seu teste, deve chamá-lo ou o teste ficará pendurado.
import { expect, test } from "bun:test";
test("2 * 2", done => {
Promise.resolve(2 * 2).then(result => {
expect(result).toEqual(4);
done();
});
});Timeouts
Opcionalmente especifique um timeout por teste em milissegundos passando um número como terceiro argumento para test.
import { test } from "bun:test";
test("wat", async () => {
const data = await slowOperation();
expect(data).toBe(42);
}, 500); // teste deve rodar em <500msEm bun:test, timeouts de teste lançam uma exceção não capturável para forçar o teste a parar e falhar. Também matamos quaisquer processos filhos que foram spawnados no teste para evitar deixar processos zumbis vagando no background.
O timeout padrão para cada teste é 5000ms (5 segundos) se não for substituído por esta opção de timeout ou jest.setDefaultTimeout().
Retries e Repeats
test.retry
Use a opção retry para tentar novamente automaticamente um teste se falhar. O teste passa se for bem-sucedido dentro do número especificado de tentativas. Isso é útil para testes instáveis que podem falhar intermitentemente.
import { test } from "bun:test";
test(
"requisição de rede instável",
async () => {
const response = await fetch("https://example.com/api");
expect(response.ok).toBe(true);
},
{ retry: 3 }, // Tentar novamente até 3 vezes se o teste falhar
);test.repeats
Use a opção repeats para executar um teste múltiplas vezes independentemente do status de passar/falhar. O teste falha se qualquer iteração falhar. Isso é útil para detectar testes instáveis ou teste de estresse. Note que repeats: N executa o teste N+1 vezes no total (1 execução inicial + N repetições).
import { test } from "bun:test";
test(
"garantir que teste é estável",
() => {
expect(Math.random()).toBeLessThan(1);
},
{ repeats: 20 }, // Executa 21 vezes no total (1 inicial + 20 repetições)
);NOTE
Você não pode usar tanto `retry` quanto `repeats` no mesmo teste.🧟 Zombie Process Killer
Quando um teste atinge o timeout e processos spawnados no teste via Bun.spawn, Bun.spawnSync ou node:child_process não são mortos, eles serão automaticamente mortos e uma mensagem será registrada no console. Isso previne que processos zumbis fiquem vagando no background após testes com timeout.
Modificadores de Teste
test.skip
Ignore testes individuais com test.skip. Estes testes não serão executados.
import { expect, test } from "bun:test";
test.skip("wat", () => {
// TODO: corrigir isso
expect(0.1 + 0.2).toEqual(0.3);
});test.todo
Marque um teste como todo com test.todo. Estes testes não serão executados.
import { expect, test } from "bun:test";
test.todo("corrigir isso", () => {
myTestFunction();
});Para executar testes todo e encontrar quaisquer que estão passando, use bun test --todo.
bun test --todomy.test.ts:
✗ funcionalidade não implementada
^ este teste é marcado como todo mas passa. Remova `.todo` ou verifique se teste está correto.
0 aprovações
1 falha
1 chamada expect()Com esta flag, testes todo com falha não causarão erro, mas testes todo que passam serão marcados como falhando para que você possa remover a marca todo ou corrigir o teste.
test.only
Para executar um teste ou suíte de testes específica use test.only() ou describe.only().
import { test, describe } from "bun:test";
test("teste #1", () => {
// não executa
});
test.only("teste #2", () => {
// executa
});
describe.only("only", () => {
test("teste #3", () => {
// executa
});
});O seguinte comando executará apenas testes #2 e #3.
bun test --onlyO seguinte comando executará apenas testes #1, #2 e #3.
bun testtest.if
Para executar um teste condicionalmente, use test.if(). O teste será executado se a condição for verdadeira. Isso é particularmente útil para testes que devem ser executados apenas em arquiteturas ou sistemas operacionais específicos.
test.if(Math.random() > 0.5)("executa metade das vezes", () => {
// ...
});
const macOS = process.platform === "darwin";
test.if(macOS)("executa no macOS", () => {
// executa se macOS
});test.skipIf
Para ignorar um teste baseado em alguma condição, use test.skipIf() ou describe.skipIf().
const macOS = process.platform === "darwin";
test.skipIf(macOS)("executa em não-macOS", () => {
// executa se *não* macOS
});test.todoIf
Se você quiser marcar o teste como TODO, use test.todoIf() ou describe.todoIf(). Escolher cuidadosamente skipIf ou todoIf pode mostrar uma diferença entre, por exemplo, intenção de "inválido para este alvo" e "planejado mas não implementado ainda."
const macOS = process.platform === "darwin";
// TODO: implementamos apenas para Linux até agora.
test.todoIf(macOS)("executa em posix", () => {
// executa se *não* macOS
});test.failing
Use test.failing() quando souber que um teste está falhando atualmente, mas quiser rastreá-lo e ser notificado quando começar a passar. Isso inverte o resultado do teste:
- Um teste com falha marcado com
.failing()passará - Um teste passando marcado com
.failing()falhará (com uma mensagem indicando que agora está passando e deve ser corrigido)
// Isso passará porque o teste está falhando como esperado
test.failing("matemática está quebrada", () => {
expect(0.1 + 0.2).toBe(0.3); // falha devido à precisão de ponto flutuante
});
// Isso falhará com uma mensagem que o teste agora está passando
test.failing("bug corrigido", () => {
expect(1 + 1).toBe(2); // passa, mas esperávamos que falhasse
});Isso é útil para rastrear bugs conhecidos que você planeja corrigir mais tarde ou para implementar desenvolvimento orientado a testes.
Testes Condicionais para Blocos Describe
Os modificadores condicionais .if(), .skipIf() e .todoIf() também podem ser aplicados a blocos describe, afetando todos os testes dentro da suíte:
const isMacOS = process.platform === "darwin";
// Executa apenas toda a suíte no macOS
describe.if(isMacOS)("funcionalidades específicas do macOS", () => {
test("funcionalidade A", () => {
// executa apenas no macOS
});
test("funcionalidade B", () => {
// executa apenas no macOS
});
});
// Ignora toda a suíte no Windows
describe.skipIf(process.platform === "win32")("funcionalidades Unix", () => {
test("funcionalidade C", () => {
// ignorado no Windows
});
});
// Marca toda a suíte como TODO no Linux
describe.todoIf(process.platform === "linux")("Suporte Linux próximo", () => {
test("funcionalidade D", () => {
// marcado como TODO no Linux
});
});Testes Parametrizados
test.each e describe.each
Para executar o mesmo teste com múltiplos conjuntos de dados, use test.each. Isso cria um teste parametrizado que é executado uma vez para cada caso de teste fornecido.
const cases = [
[1, 2, 3],
[3, 4, 7],
];
test.each(cases)("%p + %p deve ser %p", (a, b, expected) => {
expect(a + b).toBe(expected);
});Você também pode usar describe.each para criar uma suíte parametrizada que é executada uma vez para cada caso de teste:
describe.each([
[1, 2, 3],
[3, 4, 7],
])("add(%i, %i)", (a, b, expected) => {
test(`retorna ${expected}`, () => {
expect(a + b).toBe(expected);
});
test(`soma é maior que cada valor`, () => {
expect(a + b).toBeGreaterThan(a);
expect(a + b).toBeGreaterThan(b);
});
});Passagem de Argumentos
Como argumentos são passados para sua função de teste depende da estrutura dos seus casos de teste:
- Se uma linha da tabela é um array (como
[1, 2, 3]), cada elemento é passado como um argumento individual - Se uma linha não é um array (como um objeto), é passada como um único argumento
// Itens de array passados como argumentos individuais
test.each([
[1, 2, 3],
[4, 5, 9],
])("add(%i, %i) = %i", (a, b, expected) => {
expect(a + b).toBe(expected);
});
// Itens de objeto passados como um único argumento
test.each([
{ a: 1, b: 2, expected: 3 },
{ a: 4, b: 5, expected: 9 },
])("add($a, $b) = $expected", data => {
expect(data.a + data.b).toBe(data.expected);
});Especificadores de Formato
Há várias opções disponíveis para formatar o título do teste:
| Especificador | Descrição |
|---|---|
%p | pretty-format |
%s | String |
%d | Número |
%i | Inteiro |
%f | Ponto flutuante |
%j | JSON |
%o | Objeto |
%# | Índice do caso de teste |
%% | Sinal de porcentagem único (%) |
Exemplos
// Especificadores básicos
test.each([
["hello", 123],
["world", 456],
])("string: %s, número: %i", (str, num) => {
// "string: hello, número: 123"
// "string: world, número: 456"
});
// %p para saída pretty-format
test.each([
[{ name: "Alice" }, { a: 1, b: 2 }],
[{ name: "Bob" }, { x: 5, y: 10 }],
])("usuário %p com dados %p", (user, data) => {
// "usuário { name: 'Alice' } com dados { a: 1, b: 2 }"
// "usuário { name: 'Bob' } com dados { x: 5, y: 10 }"
});
// %# para índice
test.each(["apple", "banana"])("fruta #%# é %s", fruit => {
// "fruta #0 é apple"
// "fruta #1 é banana"
});Contagem de Asserções
O Bun oferece suporte à verificação de que um número específico de asserções foi chamado durante um teste:
expect.hasAssertions()
Use expect.hasAssertions() para verificar que pelo menos uma asserção é chamada durante um teste:
test("trabalho assíncrono chama asserções", async () => {
expect.hasAssertions(); // Falhará se nenhuma asserção for chamada
const data = await fetchData();
expect(data).toBeDefined();
});Isso é especialmente útil para testes assíncronos para garantir que suas asserções realmente executem.
expect.assertions(count)
Use expect.assertions(count) para verificar que um número específico de asserções é chamado durante um teste:
test("exatamente duas asserções", () => {
expect.assertions(2); // Falhará se não exatamente 2 asserções forem chamadas
expect(1 + 1).toBe(2);
expect("hello").toContain("ell");
});Isso ajuda a garantir que todas suas asserções executem, especialmente em código assíncrono complexo com múltiplos caminhos de código.
Teste de Tipo
O Bun inclui expectTypeOf para testar tipos TypeScript, compatível com Vitest.
expectTypeOf
A função expectTypeOf fornece asserções em nível de tipo que são verificadas pelo verificador de tipo do TypeScript. Para testar seus tipos:
- Escreva suas asserções de tipo usando
expectTypeOf - Execute
bunx tsc --noEmitpara verificar se seus tipos estão corretos
import { expectTypeOf } from "bun:test";
// Asserções de tipo básicas
expectTypeOf<string>().toEqualTypeOf<string>();
expectTypeOf(123).toBeNumber();
expectTypeOf("hello").toBeString();
// Correspondência de tipo de objeto
expectTypeOf({ a: 1, b: "hello" }).toMatchObjectType<{ a: number }>();
// Tipos de função
function greet(name: string): string {
return `Hello ${name}`;
}
expectTypeOf(greet).toBeFunction();
expectTypeOf(greet).parameters.toEqualTypeOf<[string]>();
expectTypeOf(greet).returns.toEqualTypeOf<string>();
// Tipos de array
expectTypeOf([1, 2, 3]).items.toBeNumber();
// Tipos de Promise
expectTypeOf(Promise.resolve(42)).resolves.toBeNumber();Para documentação completa sobre matchers expectTypeOf, veja a Referência da API.
Matchers
O Bun implementa os seguintes matchers. Compatibilidade completa com Jest está no roadmap; acompanhe o progresso aqui.
Matchers Básicos
| Status | Matcher |
|---|---|
| ✅ | .not |
| ✅ | .toBe() |
| ✅ | .toEqual() |
| ✅ | .toBeNull() |
| ✅ | .toBeUndefined() |
| ✅ | .toBeNaN() |
| ✅ | .toBeDefined() |
| ✅ | .toBeFalsy() |
| ✅ | .toBeTruthy() |
| ✅ | .toStrictEqual() |
Matchers de String e Array
| Status | Matcher |
|---|---|
| ✅ | .toContain() |
| ✅ | .toHaveLength() |
| ✅ | .toMatch() |
| ✅ | .toContainEqual() |
| ✅ | .stringContaining() |
| ✅ | .stringMatching() |
| ✅ | .arrayContaining() |
Matchers de Objeto
| Status | Matcher |
|---|---|
| ✅ | .toHaveProperty() |
| ✅ | .toMatchObject() |
| ✅ | .toContainAllKeys() |
| ✅ | .toContainValue() |
| ✅ | .toContainValues() |
| ✅ | .toContainAllValues() |
| ✅ | .toContainAnyValues() |
| ✅ | .objectContaining() |
Matchers de Número
| Status | Matcher |
|---|---|
| ✅ | .toBeCloseTo() |
| ✅ | .closeTo() |
| ✅ | .toBeGreaterThan() |
| ✅ | .toBeGreaterThanOrEqual() |
| ✅ | .toBeLessThan() |
| ✅ | .toBeLessThanOrEqual() |
Matchers de Função e Classe
| Status | Matcher |
|---|---|
| ✅ | .toThrow() |
| ✅ | .toBeInstanceOf() |
Matchers de Promise
| Status | Matcher |
|---|---|
| ✅ | .resolves() |
| ✅ | .rejects() |
Matchers de Função Mock
| Status | Matcher |
|---|---|
| ✅ | .toHaveBeenCalled() |
| ✅ | .toHaveBeenCalledTimes() |
| ✅ | .toHaveBeenCalledWith() |
| ✅ | .toHaveBeenLastCalledWith() |
| ✅ | .toHaveBeenNthCalledWith() |
| ✅ | .toHaveReturned() |
| ✅ | .toHaveReturnedTimes() |
| ✅ | .toHaveReturnedWith() |
| ✅ | .toHaveLastReturnedWith() |
| ✅ | .toHaveNthReturnedWith() |
Matchers de Snapshot
| Status | Matcher |
|---|---|
| ✅ | .toMatchSnapshot() |
| ✅ | .toMatchInlineSnapshot() |
| ✅ | .toThrowErrorMatchingSnapshot() |
| ✅ | .toThrowErrorMatchingInlineSnapshot() |
Matchers de Utilidade
| Status | Matcher |
|---|---|
| ✅ | .extend |
| ✅ | .anything() |
| ✅ | .any() |
| ✅ | .assertions() |
| ✅ | .hasAssertions() |
Ainda Não Implementado
| Status | Matcher |
|---|---|
| ❌ | .addSnapshotSerializer() |
Melhores Práticas
Use Nomes de Teste Descritivos
// Bom
test("deve calcular preço total incluindo imposto para múltiplos itens", () => {
// implementação do teste
});
// Evite
test("cálculo de preço", () => {
// implementação do teste
});Agrupe Testes Relacionados
describe("Autenticação de usuário", () => {
describe("com credenciais válidas", () => {
test("deve retornar dados do usuário", () => {
// implementação do teste
});
test("deve definir token de autenticação", () => {
// implementação do teste
});
});
describe("com credenciais inválidas", () => {
test("deve lançar erro de autenticação", () => {
// implementação do teste
});
});
});Use Matchers Apropriados
// Bom: Use matchers específicos
expect(users).toHaveLength(3);
expect(user.email).toContain("@");
expect(response.status).toBeGreaterThanOrEqual(200);
// Evite: Usar toBe para tudo
expect(users.length === 3).toBe(true);
expect(user.email.includes("@")).toBe(true);
expect(response.status >= 200).toBe(true);Teste Condições de Erro
test("deve lançar erro para entrada inválida", () => {
expect(() => {
validateEmail("not-an-email");
}).toThrow("Formato de email inválido");
});
test("deve lidar com erros assíncronos", async () => {
await expect(async () => {
await fetchUser("invalid-id");
}).rejects.toThrow("Usuário não encontrado");
});Use Setup e Teardown
import { beforeEach, afterEach, test } from "bun:test";
let testUser;
beforeEach(() => {
testUser = createTestUser();
});
afterEach(() => {
cleanupTestUser(testUser);
});
test("deve atualizar perfil do usuário", () => {
// Usar testUser no teste
});