Testing

Aprenderemos a testear nuestros componentes y proyectos de Qwik.

Curso Google - Para aprender Testing GRATIS

Curso gratuito que ofrece Google para obtener las nociones básicas sobre el Testing que os pueden venir perfectas para esta sección.

Podéis acceder al curso desde el siguiente enlace

Contenido del capítulo

Estos serán los puntos que trataremos en este capítulo, como veís hay bastantes puntos e intentaremos tocar todos los aspectos fundamentales para poder entender bien todo lo relacionado a lo que estamos viendo en el capítulo.

Introducción

La importancia de una detección efectiva y rápida de errores recae en la calidad y en la confianza que nos pueda proporcionar el software que estamos desarrollando. Los errores pueden hacer que las experiencias sean negativas para los usuarios, donde podría darse pérdida de datos o incluso vulnerabilidades de seguridad.

Lo que no podemos hacer es simplemente ir recorriendo el sitio web nosotros mismos. Necesitamos comprobar si cada unidad de nuestro código funciona como queremos que lo haga.

Para eso, vamos a necesitar escribir pruebas unitarias, y realmente pueden ser un poco molestas, dándonos bastante pereza ponernos a ello cuando realmente nos ponemos a escribirlas.

Al realizar pruebas exhaustivas durante el desarrollo, los programadores podemos (y deberíamos) identificar y abordar problemas antes de que lleguen a los usuarios finales, lo que nos hará ahorrar tiempo y recursos en correcciones posteriores.

Nuestra responsabilidad, la de los programadores, en este proceso es crucial, ya que somos los que mejor entendemos el funcionamiento interno de nuestras aplicaciones.

Además, cuando realizamos pruebas rigurosas, estamos invirtiendo en la mejora de la calidad de lo que hacemos y con ello, haremos que lo que construimos sea de más calidad y también sea más sencillo su mantenimiento futuro.

Esta responsabilidad no se va a limitar solo al presente, sino que también se extiende al futuro, sea a corto, medio o largo plazo.

El motivo es que teniendo una base sólida de pruebas nos va a facilitar la adaptación y el mantenimiento a medida que el software evoluciona con nuevas funcionalidades y actualizaciones.

En resumen, la detección temprana de errores y la inversión que realicemos en pruebas sólidas van a ser esenciales para garantizar un desarrollo de software exitoso y sostenible.

Unit testing en Qwik

Como desarrolladores responsables que somos (o deberíamos de ser), es conveniente que agreguemos pruebas unitarias a nuestras aplicaciones de Qwik.

Actualmente hay muy poca información y por ese motivo, intentaré ir ampliando esta información para abrir puertas a los futuros perfiles que trabajen con este framework.

Se realizará el aprendizaje básico para agregar pruebas unitarias a nuestras aplicaciones Qwik y dado que Qwik es relativamente nuevo, descubrí que había poca documentación sobre cómo hacerlo. Además, hasta hace poco, no existían herramientas para configurar y trabajar fácilmente con los componentes de Qwik en pruebas unitarias.

En este capítulo, os voy a ir a mostrando los pasos que hay que dar para empezar a escribir pruebas unitarias en Qwik. Explicaré el proceso a través de varios ejemplos, para ayudaros a comprender mejor los diferentes casos de uso que nos podamos encontrar normalmente en todos los proyectos y luego ya deberéis de ir adaptando lo aprendido a vuestras necesidades personales y profesionales.

Para este capítulo, asumo que ya tenéis un conocimiento básico de Qwik, ya que ya han pasado 16 capítulos del libro y si no es así, entonces os recomendaría que trabajaseis capítulo a capítulo hasta llegar aquí.

¿Cómo se estructura una prueba?

Las pruebas implican comprobar si nuestro código funciona (como se supone que debería) al comparar la salida esperada con la salida real.

Qué probar

En general, nuestras pruebas deben cubrir los siguientes aspectos de nuestro código:

  • Si un componente se representa con o sin propiedades.
  • Cómo se representa un componente con cambios de estado
  • Cómo reacciona un componente a las interacciones del usuario (por ejemplo, cambios en un input)

Qué no probar

Probar la mayor parte de tu código es importante, pero aquí hay algunas cosas que no necesitas probar:

  • Implementación real: No necesitamos probar la implementación real de una funcionalidad. Solo probaremos si el componente se comporta correctamente. Imaginaros que queremos ordenar un array al hacer click en un botón. No es necesario probar la lógica de ordenación real. Solo probamos si se ha llamado a la función y si los cambios de estado se han realizado correctamente.
  • Bibliotecas de terceros: Si estamos utilizando bibliotecas de terceros como Material UI, no va a ser necesario probarlas, ya que deberían estar probadas por sus autores.

Esto de primeras puede parecernos un poco complicado de entender, pero lo vamos a llegar a entender mejor a través de ejemplos empezando desde lo más básico.

Cualquier prueba en Qwik (u otras tecnologías como Angular, React,...), sin importar su complejidad, seguirá la siguiente estructura:

  1. Representa el componente.
  2. Obtiene un elemento del componente y simula cualquier interacción del usuario.
  3. Escribe una afirmación.

Configurando el entorno de Testing

Existen varias opciones cuando se trata de elegir un framework / herramienta de pruebas, entre las cuales existe el tan popular y utilizado Jest.

Sin embargo, dado que Qwik utiliza Vite, por simplificar el proceso vamos a hacerlo con la opción más directa y efectiva, que será usar Vitest.

Vitest está diseñado específicamente para ser la herramienta de pruebas preferida y más recomendable en proyectos Vite, lo que lo hace una excelente elección (aunque para gustos los colores, no habría problemas en usar Jest, pero podéis probarlo si así lo deseáis).

Si tenemos conocimientos con Jest, podéis alegraros, ya que la API de Vitest (https://shorten-up.vercel.app/v7j47k6nUc) es muy similar, por lo que no vamos a tener que aprender mucho para utilizarla de manera efectiva sin complicarnos mucho.

Antes de avanzar en la configuración real, asegurémonos de que tenemos un proyecto de Qwik creado para las pruebas. Si aún no lo tenemos, lo creamos con el procedimiento que ya conocemos de sobra.

Una vez que tenemos el proyecto, vamos a configurar Vitest en nuestro proyecto Qwik y este proceso lo realizaremos en tres sencillos pasos:

  1. Instalamos Vitest y las dependencias para acotar el cobertura de código analizada (coverage) como dependencias de desarrollo ejecutando el siguiente comando (Podría haber versiones más nuevas. Más info aquí: https://shorten-up.vercel.app/myAQ139lxh). En este capítulo trabajaremos con la 0.34.6 que sigue siendo compatible con la 1.4.0 de Qwik):
npm i vitest@0.34.6 @vitest/coverage-v8@0.34.6 --save-dev
  1. Actualizamos la sección scripts de nuestro fichero package.json para incluir los siguientes comandos:

Nuestra sección scripts debería verse de la siguiente manera:

"scripts": {
    ...
    "test": "vitest --run",
    "test.watch": "vitest",
    "coverage": "vitest run --coverage"
}

Después de completar estos pasos iniciales, probamos a ejecutar npm run test y npm run coverage desde nuestro terminal para verificar que Vitest está configurado correctamente.

En este momento nos dirá lo siguiente, que no se han encontrado ficheros de testing, ya que no hemos configurado ninguno todavía para testear nuestra aplicación.

En este momento nos dirá prácticamente lo mismo, ya que no hemos configurado ningún fichero para testear nuestra aplicación.

Aquí con este comando veremos la cobertura de lo que se ha testeado en % para tener una idea clara de cuanto hemos avanzado y cuanto nos quedaría. Lo ideal es andar en una cobertura cercana al 80% ó superior.


Escribiendo nuestras pruebas

Por defecto, vitest va a realizar la búsqueda de los archivos cuyos nombres coincidan con el patrón **/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}.

En un proyecto Qwik que utiliza TypeScript y devuelve elementos JSX, tiene sentido utilizar el patrón *.spec.tsx ó *.test.tsx.

Esto es cuestión de gustos y en este caso yo voy a preferir usar la terminación .spec, pero .test también funciona igual de bien y es igualmente de válido.

Sentiros libres de elegir el modo que más os guste para este apartado, ya que el funcionamiento será el mismo.

Para comenzar con la escritura de una prueba unitaria, vamos a necesitar un componente para realizar las pruebas e ir interiorizando lo que vamos a aprender sobre el testing.

A continuación, iremos de menos a más, con ejemplos aplicando el código y el posterior test unitario con el fichero *.spec.tsx (recordad que podéis usar *.test.tsx también).

Ejemplo 1: Hola Mundo sin props

Vamos a src/components, creamos primero el directorio hello y dentro de este otro basic.

Una vez creado añadimos el fichero del componente correspondiente con index.tsx y añadimos el siguiente contenido:

import { component$ } from '@builder.io/qwik';

export const Hello = component$(() => {
  return <div>Hello World with Qwik!</div>
});

Como se puede apreciar es un componente super sencillo, el más sencillo que podamos encontrar y este es un buen punto de partida.

Ahora lo que vamos a hacer es crear otro fichero en este directorio junto con el fichero index.tsx, asignándole el nombre index.spec.tsx.

Este archivo va a contener las pruebas unitarias para el componente Hello que acabamos de crear.

Ahora que tenemos nuestro componente y nuestro archivo de pruebas, podemos proceder a escribir nuestras pruebas unitarias.

Añadimos un poco de código base junto con una prueba simple (la más básica posible) para verificar que todo esté configurado correctamente:

// 1
import { createDOM } from '@builder.io/qwik/testing';
import { expect, describe, it } from 'vitest';
// 2
import { Hello } from '.';

// 3
describe("Componente Hello", function () {
// 4
    it("Debe de obtener el texto dentro del `div` y comprobar que sea cierto", async () => {
        // 5
        const { screen, render } = await createDOM();
        // 6
        await render(<Hello />);
        // 7
        const principalDiv = screen.querySelector("div");
        // 8
        expect(principalDiv?.textContent).toBe("Hello World with Qwik!");
        // 9
        expect(principalDiv?.textContent).toContain("Hello");
        expect(principalDiv?.textContent).toContain("Qwik");
        expect(principalDiv?.textContent).toContain("!");
        expect(principalDiv?.textContent).toContain("Hello World w");
    });

});

Esto sería lo que se implementa en las pruebas para verificar el funcionamiento correcto de src/components/hello/basic/index.tsx.

Antes de continuar, debemos de tener claro estos conceptos básicos:

Vitest - expect

Información de como trabajar con expect de las diferentes formas para hacer diferentes comprobaciones. Lo podemos encontrar en el siguiente enlace:
https://shorten-up.vercel.app/n7G-b_zsZj

Ahora que ya conocemos con más detalles cuales son las funciones anteriores y para que las usamos analizamos punto por punto cual es su funcionamiento de este sencillo ejemplo:

  1. Importamos los métodos necesarios para las pruebas.
  2. Importamos el componente <Hello /> para renderizarlo y testearlo.
  3. Bloque principal (describe) de lo que vamos a testear donde se pueden añadir una ó más pruebas individuales con it.
  4. Una prueba individual (it), como en este caso ver que coincide lo que se renderiza con lo que se debería de obtener.
  5. Crea el DOM del componente y obtiene el contenedor con screen y un método de representación para su renderizado con render.
  6. Llama al método de representación con el nodo JSX de nuestro componente <Hello /> como parámetro y ya disponemos de ese componente cargado para analizarlo y hacer las comprobaciones necesarias.
  7. Obtenemos el contenido del elemento div que muestra el texto introducido en ese apartado.
  8. Realizamos la comprobación con el elemento expect que cumple que el texto extraido es igual a Hello Qwik! con el comparador toBe.
  9. Comprueba con expect que contiene alguno de los textos añadidos de lo que sería todo el texto con el comparador toContain.

Probar el componente

Podéis probar el componente a mano, de la manera tradicional ejecutando primero npm start y añadiendo este componente en la ruta que deseemos consumirlo.

Esto ya lo sabemos hacer, por lo que si queréis probarlo, ya sabéis lo pasos a dar.

Ejecutamos npm run test y esto es lo que debería de mostrarse:

Aparece el resultado de que se ha testeado un fichero (Test files) y que en total (Tests) se ha realizado una prueba de test.

En los dos casos se ha validado por lo que se está cumpliendo lo que hemos especificado para dar el OK a la funcionalidad de ese componente.

Ahora sabemos que hemos pasado el test correctamente pero...

¿Hemos testeado el 100% de la funcionalidad de ese componente?

Esto lo haremos mediante la comprobación de la cobertura del código mediante el script coverage:

npm run coverage

Información amplia sobre cobertura de código (coverage)

A continuación se añade el enlace que nos permitirá ampliar la información sobre lo que es la cobertura de código (coverage) y ver algunos consejos a tener en cuenta:
https://shorten-up.vercel.app/_rhlXT0Kls

El resultado que obtenemos es el siguiente, donde se muestra lo anterior junto con una tabla donde nos dan el resultado de lo analizado en el apartado Coverage report from v8:

Encontramos las siguientes columnas:

Como se puede observar, todos los apartados están al 100%, por lo que podemos dar por buena la prueba realizada para este componente.

Si se da el caso que no llega al 100% se podría dar por válido a partir del 80%. A veces no es muy buena idea buscar el 100% debido a que vamos a invertir mucho tiempo en algo que quizás no merece la pena. Procurad que sume más del 80%

Con esto, ya hemos realizado el primer paso y lo que vamos a realizar en el siguiente punto será comprobar los diferentes resultados aplicando uso de los props.

El resultado del fichero src/components/hello/basic/index.spec.tsx lo encontraréis aquí:

https://shorten-up.vercel.app/FTPxibwn0B

Ejemplo 2: Hola Mundo haciendo uso de props

Vamos a src/components/hello y creamos el directorio props.

Una vez creado añadimos el fichero del componente correspondiente con index.tsx y añadimos el siguiente contenido:

import { component$ } from '@builder.io/qwik';

export interface IndexProps {
  name: string
}

export const HelloWithProps = component$<IndexProps>((props) => {
  return (
    <div>
      Hello { props.name} to work in Qwik!
    </div>
  );
});

Es muy parecido al anterior pero en este caso estamos usando el elemento props. Usamos la propiedad name que nos permitirá personalizar un componente en base a lo que le introduzcamos en dicha propiedad.

A continuación creamos un fichero llamado index.spec.tsx cuyo contenido será el siguiente y que analizaremos a continuación:

import { createDOM } from '@builder.io/qwik/testing';
import { expect, describe, it } from 'vitest';

import { HelloWithProps } from '.';


describe("Componente Hello with props", function () {
  it("Debería representarse correctamente con prop `name`= `Anartz`", async () => {
    const { screen, render } = await createDOM();

    // Se añade con valor 'Anartz' en 'name'
    await render(<HelloWithProps name="Anartz" />);

    const principalDiv = screen.querySelector("div");

    // Comprobamos con el valor del prop `name` añadido
    expect(principalDiv?.textContent).toBe("Hello Anartz to work in Qwik!");
  });

  it("Debería representarse correctamente con prop `name`=`Qwik`", async () => {
      
    const { screen, render } = await createDOM();

    await render(<HelloWithProps name="Qwik" />);

    const principalDiv = screen.querySelector("div");

    expect(principalDiv?.textContent).toBe("Hello Qwik to work in Qwik!");
  });
});

Se han añadido dos pruebas diferentes (con it) donde renderizamos el componente HelloWithProps introduciendo el valor de name que deseemos.

Aquí se hacen dos pruebas con Anartz y Qwik, pero podríamos probar con n posibilidades diferentes y las variantes que queramos probar.

Ejecutamos el script coverage para hacer el testeo de este nuevo componente y obtener su cobertura, cuyo resultado es el siguiente:

Y como se puede ver en el resultado:

El resultado del fichero src/components/hello/props/index.spec.tsx lo encontraréis aquí: https://shorten-up.vercel.app/qyOinujiTC.

Ejemplo 3: Renderizando una lista de elementos

Ahora que ya hemos completado los dos primeros ejemplos, vamos a seguir haciendo uso de los props con un ejemplo algo más complejo pasando una lista con varios elementos en el que haremos nuevas pruebas.

Tenemos la siguiente información para descargar que vamos a usar para generar una lista dinámicamente y lo añadimos en src/data/products.ts:

https://shorten-up.vercel.app/vJXWRJqv0J

Ahora creamos en src/components/list-products el fichero index.tsx y añadimos lo siguiente:

import { component$ } from "@builder.io/qwik";

interface ProductItem {
  name: string;
  description: string;
  category: string;
  tags: Array<string>;
  price: number;
}

interface ProductsProps {
  list: Array<ProductItem>;
}

export const List = component$<ProductsProps>((props) => {
  const { list } = props;
  return (
    <div>
      <h1>Lista de productos en venta ({list.length})</h1>
      <ul>
        {list.map((product: ProductItem) => {
          return (
            <li key={product.name.toLowerCase()} item-type="product">
              {product.name} - {product.price}
            </li>
          );
        })}
      </ul>
    </div>
  );
});

Una vez definido el fichero del componente, en el mismo directorio creamos el fichero de pruebas index.spec.tsxcon el siguiente contenido:

import { createDOM } from '@builder.io/qwik/testing';
import { expect, describe, it, beforeAll } from 'vitest';

import { List } from '.';
import { PRODUCTS } from '~/data/products';

let screenElement: HTMLElement;

describe("Componente List", function () {
  beforeAll(async() => {
      const { screen, render } = await createDOM();
      screenElement = screen;
      await render(<List list={PRODUCTS} />);
  });
  it("Obtenemos el título y comprobamos que es correcto", async () => {
      
    const h1 = screenElement.querySelector("h1");
    
    expect(h1?.textContent).toContain("Lista de productos en venta");
    expect(h1?.textContent).toEqual(`Lista de productos en venta (${PRODUCTS.length})`);
      
  });

  it("Comprobamos la lista y miramos si todo es correcto", async () => {

    const productsList = screenElement.querySelectorAll('[item-type="product"]');

    expect(productsList).toHaveLength(PRODUCTS.length);

    for (let i = 0; i < PRODUCTS.length; i++) {
        expect(productsList[i].textContent).toContain(PRODUCTS[i].name);
        expect(productsList[i].textContent).toContain(PRODUCTS[i].price);
        expect(productsList[i].textContent).toEqual(`${PRODUCTS[i].name} - ${PRODUCTS[i].price}`);
    }
  });
});

En este caso lo que hacemos es iniciar la renderización del componente con la función beforeAll antes de ejecutar las pruebas.

Como no va a cambiar la información de los elementos de la lista haciendo el proceso de renderizar una vez es más que suficiente.

En las pruebas se comprueba el contenido del título por un lado y por otro la información de los elementos de la lista como la cantidad y después analizará, elemento por elemento, si los contenidos son correctos.

Ejecutamos el comando del script para la cobertura y comprobación que las pruebas son correctas y como se puede ver a continuación cubre el 100% y aparecen los nuevos ficheros que entran en juego:

Una vez llegados a este punto, pasamos al siguiente punto donde vamos a empezar a interactuar con los cambios de estado y el ejemplo usado, será el típico contador que todos conocemos. Ejemplo que es simple y nos permitirá trabajar con los cambios de estado.

El resultado del fichero src/components/list-products/index.spec.tsx lo encontraréis aquí:

https://shorten-up.vercel.app/clay-q0OyP

Ejemplo 4: Testeando componente con uso de estados

En este nuevo apartado donde vamos a utilizar un componente de contador simple que va a estar compuesto con un div que mostrará un número y un elemento button (botón) que va a realizar la acción de +1, que servirá para incrementar el valor +1 y cuyo valor se gestionará mediante un estado con useStore (con useSignal es similar, ya hemos trabajado con estos conceptos en estados).

Seguimos los siguientes pasos para crear el componente:

  1. Dentro de componentes (src/components) creamos una nueva carpeta llamada counter y junto con ella dentro el fichero index.tsx que necesitamos.

  2. Añadimos el siguiente código:

import { component$, useStore, $ } from "@builder.io/qwik";

export const Counter = component$(() => {
  const state = useStore({
    count: 0,
  });

  const increment = $(() => state.count++);

  return (
    <>
      <button class="increment" onClick$={increment}>
        +
      </button>
      <div class="count">{state.count}</div>
    </>
  );
});
  1. Junto al archivo index.tsx, creamos el fichero index.spec.tsx.

Abrimos index.spec.tsx y añadimos el siguiente código:

import { createDOM } from "@builder.io/qwik/testing";
import { describe, expect, it } from "vitest";

import { Counter } from ".";

describe("Componente Counter", function () {
  it("debería representarse correctamente en el inicio", async () => {
      const { screen, render } = await createDOM();
      
      await render(<Counter />);
      
      const countElement = screen.querySelector(".count");
      
      expect(countElement?.textContent).toBe("0");
  });
});

Al volver a ejecutar la prueba debería de dar como resultado un pase (como en la prueba que hemos hecho antes con lo básico) si hemos realizado todo correctamente. Ahora sabemos que nuestro componente se representa y muestra el valor 0 inicial en el elemento que va a representar el contador con el 100% de cobertura.

Ejemplo 4B: Tests de Interacción en componente

Hasta este momento solo hemos probado lo que es la representación inicial de lo que es el componente y nos gustaría probar la lógica del componente, como podrían ser los cambios de estado por eventos (visto al detalle en el capítulo Eventos) como podría ser un click a cualquier botón.

Para poder realizar estas pruebas disponemos del elemento userEvent (como hemos mencionado antes simplificará la simulación de eventos de usuario) que podemos obtener desde el método createDOM().

Siguiendo el ejemplo del contador, podemos probar si este incrementa correctamente el valor del contador al hacer click en el botón +.

Aquí tenemos disponible el código actualizado:

import { createDOM } from "@builder.io/qwik/testing"; 
// importa el método createDOM
import { describe, expect, it } from "vitest";
import { Counter } from ".";

describe("Componente Counter", function () {
  it("debería representarse correctamente", async () => {
    ...
  });
  it("debería incrementarse de 0 a 1 el valor del contador al hacer UN click", async () => {
    // obtenemos el método `userEvent` junto con 
    // `screen` y `render`
    const { screen, render, userEvent } = await createDOM();

    await render(<Counter />);
    
    const countElement = screen.querySelector(".count");

    expect(countElement?.textContent).toBe("0");

    // Pasamos un selector que coincida con el botón 
    // de incremento como primer parámetro y el nombre 
    // del evento que queremos desencadenar ("click")
    // como segundo parámetro
    await userEvent("button.increment", "click");

    // Aseguramos que el contador mostrado se haya 
    // incrementado de 0 a 1 después de la
    // interacción realizada con el `userEvent`
    expect(countElement?.textContent).toBe("1");
  });
});

Con esta prueba adicional, podemos estar seguros de que nuestro componente Counter no solo se representa correctamente en la primera carga, sino que también actualizará el contador según lo esperado cuando se hace click en el botón de incremento para que sume 1 (podéis añadir para hacer alguna interacción).

Se mostrará el resultado ejecutando el script de las pruebas, con sus resultados y la cobertura cogida:

Llegados a este punto, hemos realizado tanto la comprobación de lo que es la representación inicial como comportamiento de nuestro componente.

El resultado del fichero src/components/counter/index.spec.tsx lo encontraréis aquí:

https://shorten-up.vercel.app/I7FiJu4MU-

Ejemplo 5 - Refactorizar en custom hook useCounter {#example-counter-use-counter-hook}

Vamos a basarnos en lo que tenemos del componente Counter y vamos a hacer una copia dentro de src/components copiando lo de counter a un directorio nuevo llamado counter-hook.

Una vez que tenemos esto, creamos un hook useCounter donde gestionamos las acciones y el estado del contador y esto lo hacemos dentro de src/hooks con el fichero useCounter.tsx y el siguiente contenido donde cambiamos a useSignal para controlar el estado del contador:

import { useSignal, $ } from "@builder.io/qwik";

const useCounter = (initialValue = 0) => {
  const counter = useSignal(initialValue);
  const increment = $(() => {
    counter.value++;
  });

  const reset = $(() => (counter.value = 0));
  
  return {
    counter,
    increment,
    reset,
  };
};

export { useCounter };

Una vez definido el hook personalizado, vamos a enchufarlo en src/components/counter-hook/index.tsx dejando el código de la siguiente forma donde añadimos una nueva acción, mediante un botón de Reset para poner el contador a 0:

import { component$ } from "@builder.io/qwik";
import { useCounter } from "~/hooks/useCounter";
export const CounterWithHook = component$(() => {
  const { increment, counter, reset } = useCounter(0);
  return (
    <>
      <button class="increment" onClick$={increment}>
        +
      </button>
      <button class="reset" onClick$={reset}>
        Reset
      </button>
      <div class="count">{counter.value}</div>
    </>
  );
});

Y aplicando unos cambios en la segunda prueba index.spec.tsx, donde añadimos que compruebe el resultado después de dos clicks a incrementar y un click al RESET:

import { createDOM } from "@builder.io/qwik/testing";
import { describe, expect, it } from "vitest";

import { CounterWithHook } from ".";

describe("Componente Counter - Hook", function () {
  it("debería representarse correctamente en el inicio", async () => {
      (Como antes cambiando el componente a CounterWithHook)
  });
  it("Incrementa a 2 con dos clicks y pasa a 0 con Reset", async () => {
      // obtenemos el método `userEvent` junto con `screen` y `render`
      const { screen, render, userEvent } = await createDOM();

      await render(<CounterWithHook />);

      const countElement = screen.querySelector(".count");

      expect(countElement?.textContent).toBe("0");
      
      await userEvent("button.increment", "click");
      await userEvent("button.increment", "click");
      // x 2 click en increment
      expect(countElement?.textContent).toBe("2");

      // Reset
      await userEvent("button.reset", "click");

      expect(countElement?.textContent).toBe("0");
  });
});

Ejecutando el script para verificar que todo es correcto:

Como se puede observar, seguimos con el 100% de cobertura en todos los apartados.

Hemos refactorizado manteniendo su lógica y esto ha hecho, que lo que son los test, nos den el OK por mantenerse su comportamiento. Si hubiese algún cambio en el resultado, nos habría notificado con algún error.

El resultado del fichero src/components/counter-hook/index.spec.tsx lo encontraréis aquí:

https://shorten-up.vercel.app/CGz3K2y9tO.

Ejemplo 6: Trabajando con cambios en elementos input {#example-input}

Una vez que ya hemos realizado los pasos anteriores, lo que vamos a tener en cuenta son los cambios que puedan darse de la interacción que realiza un usuario a la hora de introducir información en un elemento input.

Lo primero que haremos es crear el componente en src/componentes/input-text/index.tsx con el siguiente código:

import { component$, useSignal } from '@builder.io/qwik';

export const InputText = component$(() => {
  const githubUser = useSignal('BuilderIO');
  return (
    <main>
      <p>
        <label>
          GitHub username:
          <input
            value={githubUser.value}
            onInput$={(ev) => githubUser.value = (ev.target as HTMLInputElement).value}
          />
        </label>
      </p>
      <p>
        Select username: <span class='current-user'>{githubUser.value}</span>
      </p>
    </main>
  );
});

Donde podemos ver lo siguiente si lo añadimos en la aplicación y arrancamos la aplicación:

Lo que corresponde a lo siguiente:

Para testear esto, tenemos que hacer lo mismo que en el punto anterior, pero en vez de usar el evento click, debemos de usar el evento input dentro de los eventos de usuario (userEvent). Creamos el fichero index.spec.tsx y añadimos lo siguiente:

import { createDOM } from "@builder.io/qwik/testing"; 
import { describe, expect, it } from "vitest";
import { InputText } from ".";

describe("Componente Counter", function () {
  it("Inicia, coge los primeros datos y se realizan varias entradas de datos", async () => {
      const { screen, render, userEvent } = await createDOM();

      await render(<InputText />);

      // obtén el div que muestra el contador de nuestro contenedor
      const inputElement = screen.querySelector("input");

      // asegura que el contador mostrado sea "BuilderIO"
      // , que es el valor predeterminado
      expect(inputElement?.tagName.toLowerCase()).toBe("input");
      expect(inputElement?.textContent).toEqual("");

      expect(screen.querySelector('.current-user')?.textContent).toBe(`BuilderIO`);


      // Trabajamos con el cambio de entrada
      await userEvent(inputElement, 'input', { target: { value: "1" } });
      expect(screen.querySelector('.current-user')?.textContent).toBe(`1`);

      // Segundo cambio
      await userEvent(inputElement, 'input', { target: { value: "Anartz" } });
      expect(screen.querySelector('.current-user')?.textContent).toBe(`Anartz`);

      // Son pruebas que dicen de ser diferente a "Anartz"
      expect(screen.querySelector('.current-user')?.textContent).not.toBe(`1`);
      expect(screen.querySelector('.current-user')?.textContent).not.toBe(`BuilderIO`);
  });

});

Si hacemos un cambio en las pruebas con not.toBe y añadimos Anartz:

...
expect(screen.querySelector('.current-user')?.textContent).not.toBe(`Anartz`);
...

¿Qué créeis que ocurrirá?

Al añadir not.toBe estamos comprobando que no es igual a Anartz y en este caso sabemos que SI es igual a Anartz, por lo que al ejecutar esta prueba en concreto, nos debe de salir un error similar al siguiente:

Es bueno saber esto también ya que como se puede ver nos indica el punto donde falla, ya que estamos contradiciéndonos y así nos da opción de analizarlo y corregirlo, como es el caso, que debe de ser DIFERENTE a Anartz.

El resultado del fichero src/components/input-text/index.spec.tsx lo encontraréis aquí:

https://shorten-up.vercel.app/mtd9oDyYp5.

Mockeando hooks

Hasta ahora, tenemos una prueba que verifica la lógica del componente, que es lo mínimo que debemos de saber.

Sin embargo, puede llegar un momento en el que vamos a necesitar simular partes de la lógica del componente para probar ciertos casos de uso dependiendo del valor del estado.

Desafortunadamente, aquí pueden darse unos pequeños problemas, dentro del component$ que no expone las partes internas del componente, por lo que no podemos modificarlo fácilmente para nuestras pruebas.

Lo más común que necesitamos simular en los componentes de Qwik son los hooks como useLocation() o useStore().

Podemos hacerlo utilizando vi.mock:

https://shorten-up.vercel.app/RUfpuyi4Z_

Como se lee en la documentación de Vitest, el método vi.mock va a tomar dos argumentos:

Ejemplo 8: Mock con useStore {#example-use-store-mock}

Por poner un ejemplo, si queremos modificar el valor inicial en nuestro hook useStore() para que sea el valor inicial 1 (teniendo como referencia el ejemplo del contador que empieza desde 0), podemos simular todo el módulo @builder.io/qwik y devolver el módulo real con el valor inicial modificado, que sería el 1 mencionado.

Podemos utilizar el método bind de JavaScript para hacer esto.

La llamada a vi.mock se puede colocar en cualquier parte del código, ya que se eleva (hoisting) y siempre se llamará antes de importar módulos, pero por claridad, lo pondremos en un bloque beforeAll.

Antes de nada, vamos a separar esta implementación respecto a lo anterior creando un nuevo fichero llamado index-mock.spec.tsx en el mismo directorio donde hemos trabajado con el componente del contador (src/components/counter) añadiendo el siguiente código:

import { createDOM } from "@builder.io/qwik/testing";
// 0.- Obtenemos "vi" para hacer el mockeo del valor del contador
import { beforeAll, describe, expect, it, vi } from "vitest";
import { Counter } from ".";

// 1.- Añadimos bloque beforeAll
beforeAll(() => {
  // 2.- Simulamos useStore para que comience con el contador en 1 en lugar de 0
  // Aqí implementamos el mock del valor del useStore para empezar en 1
  vi.mock("@builder.io/qwik", async () => {
    const qwik = await vi.importActual<typeof import("@builder.io/qwik")>(
    "@builder.io/qwik"
    );
    return {
        ...qwik, // 3.- devolvemos la mayor parte del módulo sin cambios
        // 4.- utilizamos bind para establecer el estado inicial de useStore
        useStore: qwik.useStore.bind("initialState", { count: 1 }),
    };
  });
});

// 5.- Añadimos el bloque de las pruebas sabiendo que 
// el contador se inicia en 1. Esto sería como lo anterior, 
// pero empezando desde 1
describe("Componente Counter - Usando mocks", function() {
    it("debería incrementarse al hacer click partiendo con valor inicial a 1", async () => {
      // 6.- obtenemos el método `userEvent` junto con `screen` y `render`
      const { screen, render, userEvent } = await createDOM();
  
      // 7.- representamos el componente
      await render(<Counter />);
  
      // 8. - obtenemos el div que muestra el contador de nuestro contenedor
      const countElement = screen.querySelector(".count");
  
      // 9.- aseguramos que el contador mostrado sea "1", el valor predeterminado establecido por nuestra simulación
      expect(countElement?.textContent).toBe("1");
  
      // 10.- pasamos un selector que coincide con el botón de incremento como primer parámetro 
      // y el nombre del evento que queremos desencadenar ("click") como segundo parámetro
      await userEvent("button.increment", "click");
  
      // 11.- aseguramos que el contador mostrado se haya incrementado de 1 a 2
      expect(countElement?.textContent).toBe("2");
    });
});

Ejecutamos el test para ver que se realiza correctamente:

npm test

Y tendría que observarse que existe un nuevo fichero y hay una prueba adicional:

Os recomiendo que juguéis cambiando el valor que espera, el valor inicial mockeado, para que veáis que su funcionamiento es el correcto.

El resultado del fichero src/components/counter/index-mock.spec.tsx lo encontraréis aquí:

https://shorten-up.vercel.app/l4la_dhFPo

Ejemplo 9: Mock conuseLocation {#example-mock-use-location}

La simulación del hook useLocation es aún más sencilla, ya que no se espera que cambie durante una prueba.

En este caso, podemos devolver directamente un objeto desde la simulación. Aquí os proporciono un ejemplo sencillo de cómo simular el hook useLocation:

vi.mock("@builder.io/qwik", async () => {
  const qwik = await vi.importActual<typeof import("@builder.io/qwik")>(
    "@builder.io/qwik"
  );
  return {
    ...qwik,
    // return a hardcoded object for every useLocation call
    useLocation: {
      params: {},
      href: "/mock",
      pathname: "mock",
      query: {},
    }
  };
});

Para probar esto podríamos crear un componente donde accedamos a los valores de useLocation tal y como hemos trabajado en el capítulo Routing (Enrutamiento) en el apartado de useLocation.

Ejemplo 10: Probando componentes consumiendo APIs REST

En este apartado construiremos un componente sencillo donde consumiremos una API REST (visto en el capítulo de Consumir APIs REST / GraphQL con Qwik) y basándonos en dicha API haremos las pruebas correspondientes para testear componentes de este estilo.

Creamos el componente llamado DataFetchingRest en el apartado de componentes src/components/fetching/rest con un nuevo fichero index.tsx junto con uno para los estilos llamados index.css ubicado en src/components/fetching en el que le añadiremos el siguiente contenido:

Lo estructuramos en varios apartados, para explicar al detalle y hacer un repaso de lo visto anteriormene en el capítulo de Consumir APIs REST / GraphQL con Qwik.

Teniendo el fichero creado, añadimos la estructura principal del componente con los valores con los estados iniciales y los apartados especificados, para que se añadan posteriormente:

import {
  component$,
  useStore,
  Resource,
  useResource$,
  $,
  useStyles$,
} from '@builder.io/qwik';

import styles from './../index.css?inline';

export const DataFetchingRestBreakingBad = component$(() => {
  useStyles$(styles);
  const store = useStore({
    // personaje seleccionado
    idCharacter: '1',
    // Botones para seleccionar ids de personajes (50 y -1 no existen)
    idsButton: ['1', '2', '3', '4', '50', '-1'], 
  });

  const API_URL = 'http://localhost:3000';

  // http://localhost:3000/character/<ID>
  const getSelectCharacter = $(
    async (id: string, controller?: AbortController): Promise<Array<any>> => {
      (1)
    }
  );

  const selectCharacterResource = useResource$<any>(({ track, cleanup }) => {
    (2)
  });

  return (3)
});

Añadimos el código correspondiente 1:

const API_URL = 'http://localhost:3000';

// http://localhost:3000/character/<ID>
const getSelectCharacter = $(
  async (id: string, controller?: AbortController): Promise<Array<any>> => {
    
    const data = await fetch(`${API_URL}/character/${id}`, {
      method: 'GET',
      signal: controller?.signal,
    });
    const json = await data.json();

    return json ? json : [Promise.reject(json)];
  }
);

Añadimos el código correspondiente 2:

const selectCharacterResource = useResource$<any>(({ track, cleanup }) => {
  // Usamos `track` para realizar nuevas consultas cuando cambia el personaje seleccionado
  track(() => store.idCharacter);

  // La función `cleanup` se ejecuta cuando se está re-ejecutando y
  // el controlador `AbortController` puede abortar la operación anterior porque se ha interrumpido.
  const abortController = new AbortController();
  cleanup(() => abortController.abort());

  // Se obtiene la información del personaje seleccionado.
  return getSelectCharacter(store.idCharacter, abortController);
});

Añadimos el código correspondiente al 3 teniendo en cuenta el enlace donde tenemos todo el código de este fichero. Añadimos lo que está dentro de return:

"https://shorten-up.vercel.app/qL3O9Osg-q

Ahora que ya tenemos el componente creado, podemos probarlo si así lo deseamos.

Lo añadimos en src/routes/index.tsx:

import { component$ } from "@builder.io/qwik";
import { DataFetchingRestBreakingBad } from "~/components/fetching/rest";
export default component$(() => {
  return (
    <>
      <DataFetchingRestBreakingBad />
    </>
  );
});

API local en marcha

No olvidéis tener en marcha la API que hemos usado anteriormente en el capítulo 11, para que se pueda consumir sus datos y ver su funcionamiento.

Y al cargarlo esto es lo que se verá:

Vemos los resultados de varias selecciones:

Con esto, ya tenemos la base de conocimiento para poder realizar el test de este componente.

Comenzando con el test del componente

Vamos a src/components/fetch/rest y dentro de este directorio, junto con el fichero index.ts creamos el fichero index.spec.ts con esta estructura principal:


// 1.- Imports
import { createDOM } from '@builder.io/qwik/testing';
import { describe, expect, vi, beforeAll, afterAll, it} from 'vitest';
import { DataFetchingRestBreakingBad } from '.';

// D2.- Datos mock, ahora a continuación 
// creamos el fichero con los datos
import {BREAKING_BAD_CHARACTERS_DATA, BREAKING_BAD_CHARACTERS_NO_DATA, INSTRUCTIONS_DATA_BREAKING_BAD_API} from '~/data/breaking-bad';


// 3.- Gestión del estado que vamos a mockear en las pruebas `it`
// Para renderizar bien los estados de componentes
const store = {
  idCharacter: '1',
  idsButton: ['1', '2', '3', '4', '50', '-1'],
};

// 4.- Pruebas unitarias
describe('Componente - Obtener mediante `fetch`', function () {
  it('Personaje - ID 1 - Walter White', async () => {
    (A)
  });

  it('Personaje - ID 2 - Jesse Pinkman', async () => {
    (B)
  });

  it('Renderizar cuando NO encuentra un personaje seleccionado', async () => {
    (C)
  });
});

Creamos el fichero breaking-bad.ts dentro del directorio de src/data (creamos data) y añadimos la siguiente información, que serán los datos para mockear las respuestas de las peticiones fetch a nuestra API:

https://shorten-up.vercel.app/jaHs70T8j4

Testeando los datos del id 1 {#example-rest-api-test-component-id}

...
describe('Componente - Obtener mediante `fetch`', function () {
  it('Personaje - ID 1 - Walter White', async () => {
    // 1 Mock de datos de la API
    const FetchMock = vi.fn(() => ({
      json: vi.fn(() => Promise.resolve(BREAKING_BAD_CHARACTERS_DATA[0])),
    }));

    // Cuando efectua la llamada a fetch lo que se recibe
    vi.stubGlobal('fetch', FetchMock);

    // 2.- Primera carga antes de interactuar
    const { render, screen, userEvent } = await createDOM();

    await render(<DataFetchingRestBreakingBad />);
    
    // Comprobando cantidad de botones
    expect(screen.querySelectorAll('button').length).toBe(6);
    // Mientras obtiene los datos el estado que se muestra
    expect(screen.querySelector('.text-left')?.textContent).toBe('Loading...');

    // 3.- Acción de click para seleccionar un personaje
    await userEvent("button.btn-1", "click");

    // Esperamos 100ms
    await new Promise((resolve) => setTimeout(resolve, 100));

    // Comprobar que el boton está seleccionado con "select" solo para el "1"
    const btnSelectTxtContent = 
        screen.querySelector('.select')?.textContent;
    expect(btnSelectTxtContent).toContain(`1`);
    expect(btnSelectTxtContent).not.toContain(`2`);
    expect(btnSelectTxtContent).not.toContain(`3`);
    expect(btnSelectTxtContent).not.toContain(`4`);


    // 4.- Comprobamos Contenido de las instrucciones a seguir
    expect(screen.querySelector('#instructions-title')?.textContent)
      .toBe('Instrucciones a seguir');
    expect(screen.querySelector('#instructions-data')?.textContent)
      .toBe(INSTRUCTIONS_DATA_BREAKING_BAD_API);

    // Comprobar nombre del personaje seleccionado  
    expect(screen.querySelector('h4')?.textContent)
      .toBe('Walter White');
      
    // 5.- Comprobar las características <li> del personaje
    const characterInfo = screen.querySelectorAll('ul li');

    // Datos del personaje
    expect(characterInfo.length).toBe(3);

    // Lo que se espera en cada punto
    const checkInfoDetails = [
      'Descripción: Profesor de química convertido en fabricante de metanfetaminas.',
      'Nº Episodios: 62',
      'Más información: https://breakingbad.fandom.com/wiki/Walter_White'
    ];

    for (let i = 0; i < characterInfo.length; i++) {
      expect(characterInfo[i].textContent).toBe(checkInfoDetails[i]);
    }

    // 6.- Mockear el estado para seleccionar 
    // el id 2 para la siguiente prueba 
    // (actividad práctica)
    store.idCharacter = '2'
    vi.mock('@builder.io/qwik', async () => {
      const qwik = await vi.importActual<typeof import('@builder.io/qwik')>('@builder.io/qwik');
      return {
        ...qwik,
        useContext: () => ({}),
        useStore: () => ({
          ...store,
        }),
      };
    });
  });
  ...
});
  • 1: Mockeamos lo que recibiríamos en el caso de hacer la llamada a http://localhost:3000/character/1 cuya respuesta es:
{
  "id": 1,
  "name": "Walter White",
  "description": "Profesor de química convertido en fabricante de metanfetaminas.",
  "episodes": 62,
  "url": "https://breakingbad.fandom.com/wiki/Walter_White",
  "votes": 10
}

Este dato lo tenemos en el fichero de datos mock que hemos añadido en src/data/breaking-bad.ts en la constante BREAKING_BAD_CHARACTERS_DATA en la primera posición del array.

  • 2: Realizamos la primera carga donde renderizamos el componente, con el estado que hemos mockeado y en estado Loading....
  • 3: Acción de click en el botón cuyo texto es 1, para hacer la carga del personaje Walter White. Se añadirá la clase select (cambiando su estado) y se comprobará que el único, de los 4 primeros botones, el que tiene el select es el botón con el texto 1. Será el que tiene fondo naranja. Analizad entrando en las herramientas de desarrollador para ver sus propiedades.
  • 4: Hay que comprobar que se obtienen las instrucciones a seguir y el nombre del personaje, que en este caso es Walter White. Dependiendo del ID, esto hay que ir variando.
  • 5: Comprueba las características del personaje donde tenemos los selectores li dentro de ul, siendo 3 para todos los personajes que busquemos y recibamos respuesta con información.
  • 6: Mediante los conocimientos que hemos adquirido anteriormente, vamos a asignar el id que corresponde a la siguiente prueba it, para hacer correctamente el cambio de estado para cuando hagamos click en el botón. En este caso, pe

Actividad práctica - 1

Sabiendo como se implementa para el personaje cuyo id es 1.

¿Sabriáis implementarlo para el id 2 correspondiente Jesse Pinkman? Seguro que si, espero que lo intentéis. Os dejo la solución a continuación: http://shorten-up.vercel.app/rqOp46X6Kv.

Testeando cuando recibimos la opción no encontrado

Hasta ahora os he mostrado como podemos testear con la información existente de un personaje, pero no para los casos en los que pulsamos la información que nos devuelven los botones 50 o 1. Al hacer click en una de las dos opciones se muestra de la siguiente pantalla en la aplicación:

describe('Componente - Obtener mediante `fetch`', function () {
  ...
  it('Renderizar cuando NO encuentra un personaje seleccionado', async () => {
    // Mockeamos con una respuesta igual a la que recibiríamos si hacemos la llamada real.
    const FetchMock = vi.fn(() => ({
      json: vi.fn(() => Promise.resolve(BREAKING_BAD_CHARACTERS_NO_DATA)),
    }));

    // Cuando efectua la llamada a fetch (// api/f1.ts en la línea 12 y esto es lo que se recibiría para luego tratarlo)
    vi.stubGlobal('fetch', FetchMock);
    const { render, screen, userEvent } = await createDOM();

    await render(<DataFetchingRestBreakingBad />);

    expect(screen.querySelectorAll('button').length).toBe(6);
    expect(screen.querySelector('.text-left')?.textContent).toBe('Loading...');

    await userEvent("button.btn--1", "click");

    // Esperamos 100ms
    await new Promise((resolve) => setTimeout(resolve, 100));

    // Comprobar el botón seleccionado que es '-1'
    const btnSelectTxtContent = 
        screen.querySelector('.select')?.textContent;
    expect(btnSelectTxtContent).toContain(`-1`);
    expect(btnSelectTxtContent).not.toContain(`50`);

    const alertInfo = screen.querySelector('#alert-warning');
    expect(alertInfo?.outerHTML).toContain('<h4 id="alert-warning">');
    expect(alertInfo?.textContent).toBe('Personaje no encontrado');
  });
});

Después de escribir estas 3 pruebas, ejecutamos el comando test y podemos observar que se han añadido más casos de uso que están probándose:

Como se puede observar hemos pasado a 8 ficheros de los 7 que teníamos y después de añadir 3 it nuevos, pasamos a 14 pruebas que se realizan y como se puede observar, todas correctas.

El resultado del fichero src/components/fetch/rest/index.spec.tsx lo encontraréis aquí: https://shorten-up.vercel.app/surOuJl-ao

Ejemplo 11: Probando componentes consumiendo APIs GraphQL

En este apartado construiremos un componente sencillo donde consumiremos una API GraphQL (visto en el capítulo de Consumir APIs REST / GraphQL con Qwik) y basándonos en dicha API haremos las pruebas correspondientes para testear componentes de este estilo.

Lo que es la estructura del testing es lo mismo (respecto a lo definido en src/components/fetch/rest/index.spec.tsx) por lo que copiamos todo su contenido en src/components/fetch/graphql.

¿Qué es lo que va a cambiar?

Lo único que va a cambiar es el dato que hay que añadir en el mock de respuesta junto con el componente que vamos a usar, por lo que en este punto os añado esa información y luego ya lo tendréis que aplicar por vuestra cuenta.

En src/componentes/fetch/graphql/index.spec.tsx reemplazamos el texto DataFetchingRestBreakingBad que es lo referente al componente que consume la API REST al siguiente DataFetchingGraphQLBreakingBad referente al componente que consume la API GraphQL.

Es muy similar a la que hemos visto en el apartado de REST, pero aplicando la consulta usando la API GraphQL.

Teniendo en cuenta esto, pasamos al apartado del testing, donde se os explicará como debemos de aplicar el mock, usando este tipo de API.

Mockeando la respuesta de GraphQL

Imaginaros que hacemos la consulta para obtener los datos del personaje cuyo id es 1 correspondiente a Walter White.

Lo reflejaríamos de la siguiente forma:

query getSelectCharacter($id: Int!) {
  character(id: $id) {
    name
    description
    episodes
    url
    votes
    message
  }
}

// Query variables
{
    "id": 1
}

Y su respuesta será la siguiente:

{
    data: {
        character: {
            name: "Walter White",
            description: "Profesor de química convertido en fabricante de metanfetaminas.",
            episodes: 62,
            url: "https://breakingbad.fandom.com/wiki/Walter_White",
            votes: 10,
            message: null
        }
    }
}

Y esta respuesta será la que debemos de añadir, con toda la estructura, reutilizando la información de las constantes que tenemos dentro de src/data/breaking-bad.ts en la constante BREAKING_BAD_CHARACTERS_DATA en el primer índice, que corresponde al personaje cuyo id es 1 (no olvidéis respetar toda la estructura):

...
it('Personaje - ID 1 - Walter White', async () => {

    // Mockeamos con una respuesta igual a la que recibiríamos si hacemos la llamada real.
    const FetchMock = vi.fn(() => ({
      json: vi.fn(() => Promise.resolve({
        data: {
          character: {
            ...BREAKING_BAD_CHARACTERS_DATA[0],
            "message": null
          }
        }
      })),
    }));

    // Cuando efectua la llamada a fetch esto es lo que recibe
    vi.stubGlobal('fetch', FetchMock);
});
...

¿Y esto por qué es así? En el mock se "falsea" la respuesta y esto será lo que se va a obtener al hacer la consulta en el apartado de obtener los datos reflejado en la consulta:

Teniendo en cuenta esto, cuando aplicamos el mock de la respuesta dentro de cada prueba it (antes teníamos para los casos del id 1, 2 y -1) usaremos la misma estructura anterior pero aplicando la respuesta correspondiente a cada caso de uso.

Ejecutamos el script del test y esto es lo que nos debería de dar:

Como se puede observar hemos pasado a 9 ficheros de los 8 que teníamos y después de añadir 3 it nuevos, pasamos a 17 pruebas que se realizan y como se puede observar, todas correctas.

El resultado del fichero src/components/fetch/graphql/index.spec.tsx lo encontraréis aquí:

https://shorten-up.vercel.app/_Z11_qDQRU

Resultado de lo trabajado en este capítulo

El código que encontráis es el resultado final de todo el proceso realizado durante el capítulo. Os recomiendo que vayáis haciendo los pasos poco a poco y paso a paso para ir interiorizando todos los conceptos y en este caso, pienso que os vendría bien realizar pruebas con más ejemplos.

El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/BKimVBiEE1

¿Qué hemos aprendido en este capítulo?

Al finalizar este capítulo, deberíamos de ser capaces de:

Conclusion

En este capítulo, hemos explorado exhaustivamente el mundo del unit testing en Qwik utilizando Vitest.

Hemos aprendido cómo configurar nuestro entorno de desarrollo para aprovechar al máximo las capacidades de Vitest, cómo escribir pruebas unitarias efectivas y cómo integrarlas de manera fluida en nuestro flujo de trabajo de desarrollo con diferentes casos de uso.

Al adoptar prácticas sólidas de testing, no solo hemos mejorado la calidad de nuestro código, sino que también hemos ganado confianza en la estabilidad y el rendimiento de nuestras aplicaciones Qwik.

Recordad que las pruebas unitarias no solo son una herramienta para encontrar y corregir errores, sino que también van a actuar como una documentación viva de nuestro código, facilitando la colaboración y el mantenimiento a largo plazo.

No olvidéis que las pruebas unitarias son un componente esencial de la cultura de desarrollo centrada en la calidad.

Con Vitest y un enfoque sólido en el testing, estaremos preparados para construir aplicaciones Qwik más resilientes y confiables.