Define pruebas con una API similar a Jest importada del módulo integrado bun:test. A largo plazo, Bun apunta a la compatibilidad completa con Jest; por el momento, se admite un conjunto limitado de matchers expect.
Uso básico
Para definir una prueba simple:
import { expect, test } from "bun:test";
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});Agrupar pruebas
Las pruebas pueden agruparse en suites con 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);
});
});Pruebas async
Las pruebas pueden ser async.
import { expect, test } from "bun:test";
test("2 * 2", async () => {
const result = await Promise.resolve(2 * 2);
expect(result).toEqual(4);
});Alternativamente, usa la devolución de llamada done para señalar finalización. Si incluyes la devolución de llamada done como parámetro en tu definición de prueba, debes llamarla o la prueba se colgará.
import { expect, test } from "bun:test";
test("2 * 2", done => {
Promise.resolve(2 * 2).then(result => {
expect(result).toEqual(4);
done();
});
});Tiempos de espera
Opcionalmente especifica un tiempo de espera por prueba en milisegundos pasando un número como tercer argumento a test.
import { test } from "bun:test";
test("wat", async () => {
const data = await slowOperation();
expect(data).toBe(42);
}, 500); // la prueba debe ejecutarse en <500msEn bun:test, los tiempos de espera de pruebas lanzan una excepción no capturables para forzar que la prueba se detenga y falle. También matamos cualquier proceso hijo que haya sido generado en la prueba para evitar dejar procesos zombie merodeando en segundo plano.
El tiempo de espera por defecto para cada prueba es 5000ms (5 segundos) si no se anula con esta opción de tiempo de espera o jest.setDefaultTimeout().
Reintentos y repeticiones
test.retry
Usa la opción retry para reintentar automáticamente una prueba si falla. La prueba pasa si tiene éxito dentro del número especificado de intentos. Esto es útil para pruebas inestables que pueden fallar intermitentemente.
import { test } from "bun:test";
test(
"solicitud de red inestable",
async () => {
const response = await fetch("https://example.com/api");
expect(response.ok).toBe(true);
},
{ retry: 3 }, // Reintentar hasta 3 veces si la prueba falla
);test.repeats
Usa la opción repeats para ejecutar una prueba múltiples veces independientemente del estado aprobado/fallido. La prueba falla si alguna iteración falla. Esto es útil para detectar pruebas inestables o pruebas de estrés. Ten en cuenta que repeats: N ejecuta la prueba N+1 veces en total (1 ejecución inicial + N repeticiones).
import { test } from "bun:test";
test(
"asegurar que la prueba es estable",
() => {
expect(Math.random()).toBeLessThan(1);
},
{ repeats: 20 }, // Se ejecuta 21 veces en total (1 inicial + 20 repeticiones)
);NOTE
No puedes usar tanto `retry` como `repeats` en la misma prueba.🧟 Asesino de procesos zombie
Cuando una prueba excede el tiempo de espera y los procesos generados en la prueba mediante Bun.spawn, Bun.spawnSync o node:child_process no se matan, se matarán automáticamente y se registrará un mensaje en la consola. Esto evita que los procesos zombie permanezcan en segundo plano después de pruebas con tiempo de espera excedido.
Modificadores de prueba
test.skip
Omite pruebas individuales con test.skip. Estas pruebas no se ejecutarán.
import { expect, test } from "bun:test";
test.skip("wat", () => {
// TODO: arreglar esto
expect(0.1 + 0.2).toEqual(0.3);
});test.todo
Marca una prueba como pendiente con test.todo. Estas pruebas no se ejecutarán.
import { expect, test } from "bun:test";
test.todo("arreglar esto", () => {
myTestFunction();
});Para ejecutar pruebas pendientes y encontrar cualquiera que esté pasando, usa bun test --todo.
bun test --todomy.test.ts:
✗ característica no implementada
^ esta prueba está marcada como todo pero pasa. Elimina `.todo` o verifica que la prueba sea correcta.
0 pass
1 fail
1 expect() callsCon esta bandera, las pruebas todo que fallen no causarán un error, pero las pruebas todo que pasen se marcarán como fallidas para que puedas eliminar la marca todo o arreglar la prueba.
test.only
Para ejecutar una prueba o suite de pruebas particular usa test.only() o describe.only().
import { test, describe } from "bun:test";
test("prueba #1", () => {
// no se ejecuta
});
test.only("prueba #2", () => {
// se ejecuta
});
describe.only("only", () => {
test("prueba #3", () => {
// se ejecuta
});
});El siguiente comando solo ejecutará las pruebas #2 y #3.
bun test --onlyEl siguiente comando solo ejecutará las pruebas #1, #2 y #3.
bun testtest.if
Para ejecutar una prueba condicionalmente, usa test.if(). La prueba se ejecutará si la condición es verdadera. Esto es particularmente útil para pruebas que solo deben ejecutarse en arquitecturas o sistemas operativos específicos.
test.if(Math.random() > 0.5)("se ejecuta la mitad de las veces", () => {
// ...
});
const macOS = process.platform === "darwin";
test.if(macOS)("se ejecuta en macOS", () => {
// se ejecuta si macOS
});test.skipIf
Para omitir una prueba basada en alguna condición, usa test.skipIf() o describe.skipIf().
const macOS = process.platform === "darwin";
test.skipIf(macOS)("se ejecuta en no-macOS", () => {
// se ejecuta si *no* es macOS
});test.todoIf
Si en su lugar quieres marcar la prueba como TODO, usa test.todoIf() o describe.todoIf(). Elegir cuidadosamente skipIf o todoIf puede mostrar una diferencia entre, por ejemplo, intención de "inválido para este objetivo" y "planeado pero no implementado aún".
const macOS = process.platform === "darwin";
// TODO: solo hemos implementado esto para Linux hasta ahora.
test.todoIf(macOS)("se ejecuta en posix", () => {
// se ejecuta si *no* es macOS
});test.failing
Usa test.failing() cuando sabes que una prueba está fallando actualmente pero quieres rastrearla y ser notificado cuando comience a pasar. Esto invierte el resultado de la prueba:
- Una prueba fallida marcada con
.failing()pasará - Una prueba aprobada marcada con
.failing()fallará (con un mensaje indicando que ahora está pasando y debe arreglarse)
// Esto pasará porque la prueba está fallando como se espera
test.failing("las matemáticas están rotas", () => {
expect(0.1 + 0.2).toBe(0.3); // falla debido a la precisión de punto flotante
});
// Esto fallará con un mensaje de que la prueba ahora está pasando
test.failing("bug arreglado", () => {
expect(1 + 1).toBe(2); // pasa, pero esperábamos que fallara
});Esto es útil para rastrear bugs conocidos que planeas arreglar más tarde, o para implementar desarrollo dirigido por pruebas.
Pruebas condicionales para bloques Describe
Los modificadores condicionales .if(), .skipIf() y .todoIf() también pueden aplicarse a bloques describe, afectando a todas las pruebas dentro de la suite:
const isMacOS = process.platform === "darwin";
// Solo se ejecuta la suite completa en macOS
describe.if(isMacOS)("características específicas de macOS", () => {
test("característica A", () => {
// solo se ejecuta en macOS
});
test("característica B", () => {
// solo se ejecuta en macOS
});
});
// Omite la suite completa en Windows
describe.skipIf(process.platform === "win32")("características de Unix", () => {
test("característica C", () => {
// omitido en Windows
});
});
// Marca la suite completa como TODO en Linux
describe.todoIf(process.platform === "linux")("Soporte próximo de Linux", () => {
test("característica D", () => {
// marcado como TODO en Linux
});
});Pruebas parametrizadas
test.each y describe.each
Para ejecutar la misma prueba con múltiples conjuntos de datos, usa test.each. Esto crea una prueba parametrizada que se ejecuta una vez por cada caso de prueba proporcionado.
const cases = [
[1, 2, 3],
[3, 4, 7],
];
test.each(cases)("%p + %p debería ser %p", (a, b, expected) => {
expect(a + b).toBe(expected);
});También puedes usar describe.each para crear una suite parametrizada que se ejecuta una vez por cada caso de prueba:
describe.each([
[1, 2, 3],
[3, 4, 7],
])("add(%i, %i)", (a, b, expected) => {
test(`devuelve ${expected}`, () => {
expect(a + b).toBe(expected);
});
test(`la suma es mayor que cada valor`, () => {
expect(a + b).toBeGreaterThan(a);
expect(a + b).toBeGreaterThan(b);
});
});Paso de argumentos
Cómo se pasan los argumentos a tu función de prueba depende de la estructura de tus casos de prueba:
- Si una fila de tabla es un array (como
[1, 2, 3]), cada elemento se pasa como argumento individual - Si una fila no es un array (como un objeto), se pasa como un solo argumento
// Elementos de array pasados como argumentos individuales
test.each([
[1, 2, 3],
[4, 5, 9],
])("add(%i, %i) = %i", (a, b, expected) => {
expect(a + b).toBe(expected);
});
// Elementos de objeto pasados como un solo 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
Hay varias opciones disponibles para formatear el título de la prueba:
| Especificador | Descripción |
|---|---|
%p | pretty-format |
%s | String |
%d | Número |
%i | Entero |
%f | Punto flotante |
%j | JSON |
%o | Objeto |
%# | Índice del caso de prueba |
%% | Signo de porcentaje único (%) |
Ejemplos
// Especificadores básicos
test.each([
["hola", 123],
["mundo", 456],
])("string: %s, número: %i", (str, num) => {
// "string: hola, número: 123"
// "string: mundo, número: 456"
});
// %p para salida pretty-format
test.each([
[{ name: "Alice" }, { a: 1, b: 2 }],
[{ name: "Bob" }, { x: 5, y: 10 }],
])("usuario %p con datos %p", (user, data) => {
// "usuario { name: 'Alice' } con datos { a: 1, b: 2 }"
// "usuario { name: 'Bob' } con datos { x: 5, y: 10 }"
});
// %# para índice
test.each(["manzana", "plátano"])("fruta #%# es %s", fruit => {
// "fruta #0 es manzana"
// "fruta #1 es plátano"
});Conteo de aserciones
Bun admite verificar que se llame un número específico de aserciones durante una prueba:
expect.hasAssertions()
Usa expect.hasAssertions() para verificar que se llame al menos una aserción durante una prueba:
test("trabajo async llama aserciones", async () => {
expect.hasAssertions(); // Fallará si no se llama ninguna aserción
const data = await fetchData();
expect(data).toBeDefined();
});Esto es especialmente útil para pruebas async para asegurar que tus aserciones realmente se ejecuten.
expect.assertions(count)
Usa expect.assertions(count) para verificar que se llame un número específico de aserciones durante una prueba:
test("exactamente dos aserciones", () => {
expect.assertions(2); // Fallará si no se llaman exactamente 2 aserciones
expect(1 + 1).toBe(2);
expect("hola").toContain("ell");
});Esto ayuda a asegurar que todas tus aserciones se ejecuten, especialmente en código async complejo con múltiples rutas de código.
Pruebas de tipos
Bun incluye expectTypeOf para probar tipos de TypeScript, compatible con Vitest.
expectTypeOf
La función expectTypeOf proporciona aserciones a nivel de tipo que son verificadas por el verificador de tipos de TypeScript. Para probar tus tipos:
- Escribe tus aserciones de tipo usando
expectTypeOf - Ejecuta
bunx tsc --noEmitpara verificar que tus tipos sean correctos
import { expectTypeOf } from "bun:test";
// Aserciones de tipo básicas
expectTypeOf<string>().toEqualTypeOf<string>();
expectTypeOf(123).toBeNumber();
expectTypeOf("hola").toBeString();
// Coincidencia de tipo de objeto
expectTypeOf({ a: 1, b: "hola" }).toMatchObjectType<{ a: number }>();
// Tipos de función
function greet(name: string): string {
return `Hola ${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 documentación completa sobre matchers expectTypeOf, consulta la Referencia de API.
Matchers
Bun implementa los siguientes matchers. La compatibilidad completa con Jest está en la hoja de ruta; rastrea el progreso aquí.
Matchers básicos
| Estado | Matcher |
|---|---|
| ✅ | .not |
| ✅ | .toBe() |
| ✅ | .toEqual() |
| ✅ | .toBeNull() |
| ✅ | .toBeUndefined() |
| ✅ | .toBeNaN() |
| ✅ | .toBeDefined() |
| ✅ | .toBeFalsy() |
| ✅ | .toBeTruthy() |
| ✅ | .toStrictEqual() |
Matchers de string y array
| Estado | Matcher |
|---|---|
| ✅ | .toContain() |
| ✅ | .toHaveLength() |
| ✅ | .toMatch() |
| ✅ | .toContainEqual() |
| ✅ | .stringContaining() |
| ✅ | .stringMatching() |
| ✅ | .arrayContaining() |
Matchers de objeto
| Estado | Matcher |
|---|---|
| ✅ | .toHaveProperty() |
| ✅ | .toMatchObject() |
| ✅ | .toContainAllKeys() |
| ✅ | .toContainValue() |
| ✅ | .toContainValues() |
| ✅ | .toContainAllValues() |
| ✅ | .toContainAnyValues() |
| ✅ | .objectContaining() |
Matchers de número
| Estado | Matcher |
|---|---|
| ✅ | .toBeCloseTo() |
| ✅ | .closeTo() |
| ✅ | .toBeGreaterThan() |
| ✅ | .toBeGreaterThanOrEqual() |
| ✅ | .toBeLessThan() |
| ✅ | .toBeLessThanOrEqual() |
Matchers de función y clase
| Estado | Matcher |
|---|---|
| ✅ | .toThrow() |
| ✅ | .toBeInstanceOf() |
Matchers de Promise
| Estado | Matcher |
|---|---|
| ✅ | .resolves() |
| ✅ | .rejects() |
Matchers de función mock
| Estado | Matcher |
|---|---|
| ✅ | .toHaveBeenCalled() |
| ✅ | .toHaveBeenCalledTimes() |
| ✅ | .toHaveBeenCalledWith() |
| ✅ | .toHaveBeenLastCalledWith() |
| ✅ | .toHaveBeenNthCalledWith() |
| ✅ | .toHaveReturned() |
| ✅ | .toHaveReturnedTimes() |
| ✅ | .toHaveReturnedWith() |
| ✅ | .toHaveLastReturnedWith() |
| ✅ | .toHaveNthReturnedWith() |
Matchers de snapshot
| Estado | Matcher |
|---|---|
| ✅ | .toMatchSnapshot() |
| ✅ | .toMatchInlineSnapshot() |
| ✅ | .toThrowErrorMatchingSnapshot() |
| ✅ | .toThrowErrorMatchingInlineSnapshot() |
Matchers de utilidad
| Estado | Matcher |
|---|---|
| ✅ | .extend |
| ✅ | .anything() |
| ✅ | .any() |
| ✅ | .assertions() |
| ✅ | .hasAssertions() |
Aún no implementado
| Estado | Matcher |
|---|---|
| ❌ | .addSnapshotSerializer() |
Mejores prácticas
Usar nombres de prueba descriptivos
// Bueno
test("debería calcular el precio total incluyendo impuestos para múltiples artículos", () => {
// implementación de prueba
});
// Evitar
test("cálculo de precio", () => {
// implementación de prueba
});Agrupar pruebas relacionadas
describe("Autenticación de usuario", () => {
describe("con credenciales válidas", () => {
test("debería devolver datos de usuario", () => {
// implementación de prueba
});
test("debería establecer token de autenticación", () => {
// implementación de prueba
});
});
describe("con credenciales inválidas", () => {
test("debería lanzar error de autenticación", () => {
// implementación de prueba
});
});
});Usar matchers apropiados
// Bueno: Usar matchers específicos
expect(users).toHaveLength(3);
expect(user.email).toContain("@");
expect(response.status).toBeGreaterThanOrEqual(200);
// Evitar: Usar toBe para todo
expect(users.length === 3).toBe(true);
expect(user.email.includes("@")).toBe(true);
expect(response.status >= 200).toBe(true);Probar condiciones de error
test("debería lanzar error para entrada inválida", () => {
expect(() => {
validateEmail("not-an-email");
}).toThrow("Formato de email inválido");
});
test("debería manejar errores async", async () => {
await expect(async () => {
await fetchUser("invalid-id");
}).rejects.toThrow("Usuario no encontrado");
});Usar configuración y limpieza
import { beforeEach, afterEach, test } from "bun:test";
let testUser;
beforeEach(() => {
testUser = createTestUser();
});
afterEach(() => {
cleanupTestUser(testUser);
});
test("debería actualizar perfil de usuario", () => {
// Usar testUser en prueba
});