Skip to content

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:

math.test.ts
ts
import { expect, test } from "bun:test";

test("2 + 2", () => {
  expect(2 + 2).toBe(4);
});

Agrupar pruebas

Las pruebas pueden agruparse en suites con describe.

math.test.ts
ts
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.

math.test.ts
ts
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á.

math.test.ts
ts
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.

math.test.ts
ts
import { test } from "bun:test";

test("wat", async () => {
  const data = await slowOperation();
  expect(data).toBe(42);
}, 500); // la prueba debe ejecutarse en <500ms

En 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.

example.test.ts
ts
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).

example.test.ts
ts
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.

math.test.ts
ts
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.

math.test.ts
ts
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.

bash
bun test --todo
my.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() calls

Con 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().

example.test.ts
ts
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.

bash
bun test --only

El siguiente comando solo ejecutará las pruebas #1, #2 y #3.

bash
bun test

test.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.

example.test.ts
ts
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().

example.test.ts
ts
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".

example.test.ts
ts
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)
ts
// 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:

example.test.ts
ts
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.

math.test.ts
ts
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:

sum.test.ts
ts
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
example.test.ts
ts
// 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:

EspecificadorDescripción
%ppretty-format
%sString
%dNúmero
%iEntero
%fPunto flotante
%jJSON
%oObjeto
%#Índice del caso de prueba
%%Signo de porcentaje único (%)

Ejemplos

example.test.ts
ts
// 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:

example.test.ts
ts
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:

example.test.ts
ts
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:

  1. Escribe tus aserciones de tipo usando expectTypeOf
  2. Ejecuta bunx tsc --noEmit para verificar que tus tipos sean correctos
example.test.ts
ts
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

EstadoMatcher
.not
.toBe()
.toEqual()
.toBeNull()
.toBeUndefined()
.toBeNaN()
.toBeDefined()
.toBeFalsy()
.toBeTruthy()
.toStrictEqual()

Matchers de string y array

EstadoMatcher
.toContain()
.toHaveLength()
.toMatch()
.toContainEqual()
.stringContaining()
.stringMatching()
.arrayContaining()

Matchers de objeto

EstadoMatcher
.toHaveProperty()
.toMatchObject()
.toContainAllKeys()
.toContainValue()
.toContainValues()
.toContainAllValues()
.toContainAnyValues()
.objectContaining()

Matchers de número

EstadoMatcher
.toBeCloseTo()
.closeTo()
.toBeGreaterThan()
.toBeGreaterThanOrEqual()
.toBeLessThan()
.toBeLessThanOrEqual()

Matchers de función y clase

EstadoMatcher
.toThrow()
.toBeInstanceOf()

Matchers de Promise

EstadoMatcher
.resolves()
.rejects()

Matchers de función mock

EstadoMatcher
.toHaveBeenCalled()
.toHaveBeenCalledTimes()
.toHaveBeenCalledWith()
.toHaveBeenLastCalledWith()
.toHaveBeenNthCalledWith()
.toHaveReturned()
.toHaveReturnedTimes()
.toHaveReturnedWith()
.toHaveLastReturnedWith()
.toHaveNthReturnedWith()

Matchers de snapshot

EstadoMatcher
.toMatchSnapshot()
.toMatchInlineSnapshot()
.toThrowErrorMatchingSnapshot()
.toThrowErrorMatchingInlineSnapshot()

Matchers de utilidad

EstadoMatcher
.extend
.anything()
.any()
.assertions()
.hasAssertions()

Aún no implementado

EstadoMatcher
.addSnapshotSerializer()

Mejores prácticas

Usar nombres de prueba descriptivos

example.test.ts
ts
// 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

auth.test.ts
ts
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

auth.test.ts
ts
// 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

example.test.ts
ts
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

example.test.ts
ts
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
});

Bun por www.bunjs.com.cn editar