Saltar al contenido principal

Matchers de Jest

Esta es una lista, para nada exhaustiva, de los matchers que tiene Jest (métodos para verificar condiciones en los tests).

Para ver otros métodos, recomendamos leer la documentación.

Básicos#

SintaxisSirve para verificar...
expect(objeto).toBe(otro)...si un objeto el mismo que otro (por identidad).
expect(objeto).toEqual(otro)...si un objeto es igual a otro (por igualdad).
expect(booleano).toBeTruthy()...si un booleano es verdadero.
expect(booleano).toBeFalsy()...si un booleano es falso.
tip

Todos los matchers se pueden negar anteponiendo .not:

  • expect(numero).not.toBe(21)
  • expect(lista).not.toEqual([]).

¿Igualdad o identidad?#

En el paradigma de objetos existen dos conceptos que a menudo se confunden, pero tienen significados bien diferentes: la identidad y la igualdad. No tener clara esta diferencia puede resultar particularmente molesto a la hora de escribir pruebas con Jest, porque debemos utilizar métodos distintos según qué querramos probar.

Conceptualmente, la identidad se refiere a la capacidad de un objeto de "saber quién es", mientras que la igualdad es una característica configurable, que tiene que ver con comparar atributos para saber si dos objetos son similares.

Lo más habitual es utilizar toEqual, excepto que lo que se quiera verificar es efectivamente que el objeto es el mismo. Para tipos primitivos (números, strings, etc) ambos métodos funcionan de la misma manera.

Veamos un ejemplo, donde el primer test falla y el otro pasa:

describe('Identidad VS igualdad', () => {  const juanaAzurduy = { nombre: 'Juana' };  const juanaDeArco = { nombre: 'Juana' };
  // ❌ Failed  it('toBe compara por identidad', () => {    expect(juanaAzurduy).toBe(juanaDeArco);    /*    Error: expect(received).toBe(expected) // Object.is equality    If it should pass with deep equality, replace "toBe" with "toStrictEqual"
    Expected: {"nombre": "Juana"}    Received: serializes to the same string    */  });
  // ✅ Passed  it('toEqual compara por igualdad', () => {    expect(juanaAzurduy).toEqual(juanaDeArco);  });});

Un caso especial: números decimales#

En informática, las operaciones con decimales suelen implementarse con una técnica llamada punto flotante. Esta implementación trae aparejada una serie de imprecisiones, que afectan entre otras cosas a la comparación.

Para evitar casos en los que un test rompa porque "se esperaba 2.78 pero se obtuvo 2.78000000001", estas comparaciones se suelen hacer con una cierta tolerancia, que indica cuál es el desfasaje máximo aceptable.

En Jest, la tolerancia se configura en la forma de ¿cuántos dígitos decimales me interesan?:

// Esta cuenta dará algo como 2.78000000012const numero = 2.1 + 0.68;
// Por defecto, solo mira los 2 primeros dígitos e ignora el restoexpect(numero).toBeCloseTo(2.781);
// Pero ese valor se puede modificar si queremos que considere más decimalesexpect(numero).toBeCloseTo(2.78001, 4);

Colecciones#

SintaxisSirve para verificar...
expect(coleccion).toHaveLength(numero)...si la colección tiene numero elementos.
expect(coleccion).toContain(elemento)...si la colección contiene al elemento (por identidad).
expect(coleccion).toContainEqual(elemento)...si la colección contiene al elemento (por igualdad).

👀 Ojo: para entender la diferencia entre los ultimos dos, ver la sección ¿Igualdad o identidad?.

Excepciones#

Para chequear que un método o función falla se utiliza el matcher toThrow en cualquiera de sus variantes:

// Solo verifica que falleexpect(  () => objeto.metodoQueDeberiaFallar()).toThrow();
// Verifica que falle con cierto mensajeexpect(  () => objeto.metodoQueDeberiaFallar()).toThrow('Ups, esto no se pudo hacer');

Matchers parciales#

Hay veces que puede resultar molesto tener que crear un objeto cuando en verdad solo nos interesan algunos atributos. En casos así podemos utilizar el matcher toMatchObject, que solamente verifica que el objeto en cuestión tiene los atributos que nos interesan (ignorando los demás).

Veamos un ejemplo:

it('tiene las dimensiones adecuadas', () => {  const mesa = {    ancho: 50,    largo: 150,    madera: 'lapacho',    color: 'natural',  };
  // Solo comprueba que ancho y largo tengan los valores especificados.  expect(mesa).toMatchObject({ ancho: 50, largo: 150 });});

Este matcher funciona también para listas, y lo que hace en este caso es comparar uno a uno, en orden, los elementos, teniendo en cuenta solamente los atributos especificados. Si las listas no tienen la misma cantidad de elementos, falla.

describe('toMatchObject en listas', () => {  const mesaLapacho = {    ancho: 50,    largo: 150,    madera: 'lapacho',    color: 'natural',  };
  const mesaPino = {    ancho: 60,    largo: 145,    madera: 'pino',    color: 'blanco',  };
  // ✔️ Passed  it('compara atributos, en orden', () => {    expect([mesaPino, mesaLapacho]).toMatchObject([      { madera: 'pino' },      { madera: 'lapacho' },    ]);  });
  // ❌ Failed  it('falla si los elementos están en otro orden', () => {    expect([mesaPino, mesaLapacho]).toMatchObject([      { madera: 'lapacho' },      { madera: 'pino' },    ]);  });
  // ❌ Failed  it('falla si la cantidad de elementos no coincide', () => {    expect([mesaPino, mesaLapacho]).toMatchObject([{ madera: 'pino' }]);  });});

Objetos compartidos entre tests#

Muchas veces es deseable compartir objetos entre distintos tests, para no repetir el código que los crea en cada uno de ellos.

En Jest, cada bloque describe e it define también un alcance (o scope), compartido por todos los tests que estén dentro. En términos prácticos, esto implica que cualquier valor definido dentro de uno de estos bloques puede ser accedido dentro de él.

Veamos un ejemplo:

describe('Dirección de tránsito', () => {  const fechaEmision = DateTime.local(2021, 3, 8);  const camionetaBlanca = new Camioneta('Mercedes Sprinter 313', Color.BLANCO);
  describe('emite licencias profesionales', () => {    const licenciaProfesional = new LicenciaProfesional(fechaEmision);    it('aptas para conducir una camioneta', () => {      // Acá se pueden usar todas las definidas en los describe.      expect(        licenciaProfesional.sirveParaConducir(camionetaBlanca)      ).toBeTruthy();    });
    it('aptas para conducir un automóvil', () => {      // Lo mismo en este caso, sumando fititoRojo que se define directamente acá.      const fititoRojo = new Auto('Fiat 600', Color.ROJO);      expect(licenciaProfesional.sirveParaConducir(fititoRojo)).toBeTruthy();    });  });});

Reiniciar el estado#

La solución que mostramos recién tiene un potencial problema: los objetos que definimos se crean una sola vez, y si algún test modifica su estado interno esto afectará a todos los demás. Siguiendo con el ejemplo, si un test inhabilitara una licencia por infracciones, esta quedaría inhabilitada para todos los demás.

Para que esto no ocurra, Jest tiene la posibilidad de definir bloques de inicialización que se ejecutan antes de cada test. En esos bloques, llamados beforeEach conviene inicializar todos los objetos que sabemos que pueden cambiar durante la ejecución de los tests. Podemos omitir aquellos que no vayan a cambiar, siempre y cuando tengamos la certeza de que sea así. Encontrar errores producidos por tests dependientes puede ser un gran dolor de cabeza. 😰.

describe('Dirección de tránsito', () => {  // Asumimos que la fecha y la camioneta no cambian, los dejamos así.  const fechaEmision = DateTime.local(2021, 3, 8);  const camionetaBlanca = new Camioneta('Mercedes Sprinter 313', Color.BLANCO);
  describe('emite licencias profesionales', () => {    // Como la vamos a inicializar más tarde, debemos convertirla en una variable.    let licenciaProfesional: Licencia;
    beforeEach(() => {      // Acá le damos su valor inicial. Esto se ejecuta antes de cada `it`.      licenciaProfesional = new LicenciaProfesional(fechaEmision);    });
    it('no aptas cuando están inhabilitadas', () => {      // Inhabilitar la licencia no afectará a los siguientes tests,      // porque el objeto se vuelve a crear cada vez.      licenciaProfesional.inhabilitar('Acumulación de infracciones');      expect(        licenciaProfesional.sirveParaConducir(camionetaBlanca)      ).toBeFalsy();    });
    it('aptas para conducir una camioneta', () => {      expect(        licenciaProfesional.sirveParaConducir(camionetaBlanca)      ).toBeTruthy();    });
    it('aptas para conducir un automóvil', () => {      const fititoRojo = new Auto('Fiat 600', Color.ROJO);      expect(licenciaProfesional.sirveParaConducir(fititoRojo)).toBeTruthy();    });  });});