Aprenderemos a testear nuestros componentes y proyectos de Qwik.
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
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.
Unit testing en Qwik.
Configurando el entorno de Testing.
Escribiendo nuestras pruebas.
Ejemplos con diferentes casos de uso.
Conclusió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.
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í.
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:
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:
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:
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
scripts
de nuestro fichero package.json
para incluir los siguientes comandos:test
): vitest --run
.test.watch
): vitest
.coverage
: vitest run --coverage
.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.
npm run test
(yarn test
):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.
npm run coverage
(yarn coverage
):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.
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).
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:
it
/ test
: Pasaríamos una función a este método y el encargado de ejecutar las pruebas ejecutará esa función como un bloque de pruebas.describe
: Este método opcional es para agrupar cualquier número de declaraciones o declaraciones de prueba (las que hacemos con it
o test
).expect
: Función que usamos como la condición que la prueba debe de pasar. Compara el parámetro recibido con el comparador que está compuesto por diferentes funciones de comparación como toEqual
, toContain
, etc.render
: Este método representa el DOM completo, incluidos los componentes secundarios, uso de los hooks del componente principal, en el que estaremos ejecutando las pruebas. Lo obtenemos desde la función createDOM()
que la importamos del paquete @builder.io/qwik/testing
.screen
: Facilita la interacción y acceso a elementos de la interfaz durante las pruebas de usuario. Como en render
obtenemos este elemento desde la función createDOM()
del paquete ya mencionado.userEvent
: Simplifica la simulación de eventos de usuario, como clicks, entradas de texto y etc. para realizar pruebas de interfaz de usuario más realistas y robustas. Lo iremos viendo en los siguiente ejemplos.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:
<Hello />
para renderizarlo y testearlo.describe
) de lo que vamos a testear donde se pueden añadir una ó más pruebas individuales con it
.it
), como en este caso ver que coincide lo que se renderiza con lo que se debería de obtener.screen
y un método de representación para su renderizado con render
.<Hello />
como parámetro y ya disponemos de ese componente cargado para analizarlo y hacer las comprobaciones necesarias.div
que muestra el texto introducido en ese apartado.expect
que cumple que el texto extraido es igual a Hello Qwik!
con el comparador toBe
.expect
que contiene alguno de los textos añadidos de lo que sería todo el texto con el comparador toContain
.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
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:
File
: Referencia del fichero que está analizando, en este caso aparece únicamente index.tsx
sin hacer referencia a su directorio, ya que es el único elemento de prueba. Cuando añadamos más ficheros ya especificará correctamente el correspondiente a cada uno.% Stmts
: Porcentaje de declaraciones de código que han sido ejecutadas durante las pruebas en comparación con el total de declaraciones de código en el programa. Este valor indica la cobertura de código lograda por las pruebas. Es una métrica clave para evaluar la efectividad de las pruebas. Un valor del 100% significa que todas las líneas de código han sido ejecutadas al menos una vez durante las pruebas, lo que implica una cobertura completa. Un valor menor indica que algunas partes del código no han sido ejecutadas y podrían no estar siendo probadas adecuadamente. La cobertura de código es una herramienta importante para medir la calidad y exhaustividad de las pruebas en un proyecto de desarrollo de software.% Branch
: Resultado del coverage que indica el porcentaje de ramificaciones condicionales ejecutadas en relación con el total en nuestro programa. Un 100% significa cobertura completa de decisiones condicionales, mientras que un valor menor indica ramificaciones no totalmente probadas. Las ramificaciones condicionales incluyen estructuras como if
, else
, switch
, y otros bloques de código que pueden tener múltiples caminos de ejecución. Como en el apartado anterior 100% significa que se ha evaluado mínimo todas las ramificaciones. Un valor menor indica que no se ha evaluado todo.% Funcs
: Funciones que se comprueban. Aquí si entra en una función suma a la cobertura.% Lines
: Líneas concretas. Puede estar dentro de una función pero no acceder ya que está dentro de un condicional que nunca se tiene en cuenta en las pruebas.% Uncovered Line #s
: Nos aparecerá que líneas no se han comprobado / acotado, por lo que debemos de analizar y testearlo. Esta información se muestra cuando alguna de las columnas anteriores no llegan al 100%. Si todas llegan a ese objetivo, no habrá nada que informar y estará vacio ese valor.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í:
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:
1
: tenemos con (2)
el número de pruebas asociadas al componente con props
especificadas con it
.2
: tenemos con (1)
el número de pruebas asociadas al componente básico especificado con it
.3
: resumen de los ficheros que se testean que en este caso son 2 y también número total de pruebas que serán 3 (especificado con it
).4
: La cobertura de lo testeado, donde aparece con más detalle los ficheros con su ruta y como se puede observar, obtiene el 100% de cobertura, por lo que la columna Uncovered Line
estará vacio al no tener nada que comentar.El resultado del fichero src/components/hello/props/index.spec.tsx
lo encontraréis aquí: https://shorten-up.vercel.app/qyOinujiTC.
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.tsx
con 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í:
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:
Dentro de componentes (src/components
) creamos una nueva carpeta llamada counter
y junto con ella dentro el fichero index.tsx
que necesitamos.
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>
</>
);
});
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.
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í:
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í:
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:
1
: Apartado donde introducimos los cambios2
: Elemento donde se reflejan los cambios introducidos en el input
(1
).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í:
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:
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í:
useLocation
{#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.
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:
index.css
(este fichero lo usaremos también en el consumo de la API GraphQL
) y su código lo tenemos a continuación: https://shorten-up.vercel.app/hHlEApwzuq
index.tsx
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)
});
1
: Función donde hacemos la llamada a la API y obtenemos la información.2
: Uso de useResource$()
. Si no recordáis como funciona, os invito a que vayáis al capítulo de Consumir APIs REST / GraphQL con Qwik.3
: Código JSX con los diferentes estados dependiendo de lo que obtengamos desde el estado del recurso, como en estado de carga, resuelto o rechazado.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 />
</>
);
});
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á:
1
: Botones de acción para seleccionar un personaje. Esta información se ha establecido en el store
en la propiedad idsButton
cuya información asignada es ['1', '2', '3', '4', '50', '-1'],
. En este caso 50
y -1
serán ids
que no existen y darán el resultado Personaje no encontrado
. Por defecto está seleccionado el id 1
.2
: Apartado donde se muestran las instrucciones de uso de este componente junto con el resultado del personaje seleccionado. Al estar seleccionado el 1
nos carga la información relacionada a Walter White
.Vemos los resultados de varias selecciones:
4
50
(en -1
funciona igual)Con esto, ya tenemos la base de conocimiento para poder realizar el test de este 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:
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, peSabiendo 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.
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
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.
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
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
Al finalizar este capítulo, deberíamos de ser capaces de:
useStore
, useLocation
y fetch
.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.