Saltar al contenido principal

Pruebas unitarias de composables

Los composables extraen la lógica de datos de los componentes, por lo que suelen ser el lugar más sencillo para probarla. renderDataCompose() de @data-client/vue/test ejecuta un composable dentro de un componente montado con DataClientPlugin y Fixtures. Acepta las mismas opciones y necesita la misma configuración que mountDataClient().

renderDataCompose()​

renderDataCompose() es asíncrono; haz await antes de leer result, que es lo que devolvió el composable. useQuery() y useCache() devuelven un ComputedRef, así que sus datos están en result.value:

import { renderDataCompose } from '@data-client/vue/test';
import { useQuery } from '@data-client/vue';
import { Article, ArticleResource } from './resources';

it('useQuery() returns cached data', async () => {
const { result, cleanup } = await renderDataCompose(
() => useQuery(Article, { id: 5 }),
{
initialFixtures: [
{
endpoint: ArticleResource.get,
args: [{ id: 5 }],
response: { id: 5, title: 'hi ho', content: 'whatever' },
},
],
},
);

expect(result.value?.title).toBe('hi ho');
cleanup();
});

Llama siempre a cleanup() al final de una prueba; desmonta la aplicación y sus managers.

useSuspense()​

useSuspense() devuelve una Promise que se resuelve a un ComputedRef cuando los datos están disponibles. Haz await una sola vez y luego lee .value; la ref sigue siendo reactiva a medida que cambia el store.

import { renderDataCompose } from '@data-client/vue/test';
import { useSuspense } from '@data-client/vue';
import { nextTick } from 'vue';
import { ArticleResource } from './resources';

it('useSuspense() follows store updates', async () => {
const { result, controller, cleanup } = await renderDataCompose(
() => useSuspense(ArticleResource.get, { id: 5 }),
{
initialFixtures: [
{
endpoint: ArticleResource.get,
args: [{ id: 5 }],
response: { id: 5, title: 'hi ho', content: 'whatever' },
},
],
},
);

const article = await result;
expect(article.value.title).toBe('hi ho');

controller.setResponse(
ArticleResource.get,
{ id: 5 },
{ id: 5, title: 'updated', content: 'whatever' },
);
await nextTick();
expect(article.value.title).toBe('updated');

cleanup();
});

Agrega error: true a un fixture para probar fallos; la Promise se rechaza entonces con ese error:

it('rejects on a failed fetch', async () => {
const { result, cleanup } = await renderDataCompose(
() => useSuspense(ArticleResource.get, { id: 5 }),
{
resolverFixtures: [
{
endpoint: ArticleResource.get,
args: [{ id: 5 }],
response: { status: 404 },
error: true,
},
],
},
);

await expect(result).rejects.toMatchObject({ status: 404 });
cleanup();
});

Cambiar props​

El composable recibe props. Pasa un objeto reactive() y entrégale al composable un getter o un computed() construido a partir de él para que siga los cambios:

import { reactive } from 'vue';

it('follows prop changes', async () => {
const props = reactive({ id: 1 });
const { result, cleanup } = await renderDataCompose(
(props: { id: number }) => useQuery(Article, () => ({ id: props.id })),
{
props,
initialFixtures: [
{
endpoint: ArticleResource.get,
args: [{ id: 1 }],
response: { id: 1, title: 'First', content: '' },
},
{
endpoint: ArticleResource.get,
args: [{ id: 2 }],
response: { id: 2, title: 'Second', content: '' },
},
],
},
);
expect(result.value?.title).toBe('First');

props.id = 2;
await nextTick();
expect(result.value?.title).toBe('Second');

cleanup();
});

Mutaciones​

El controller devuelto es el Controller de la aplicación. Tras una mutación, haz await y luego nextTick() antes de comprobar el resultado:

it('push() adds to the list', async () => {
const { result, controller, cleanup } = await renderDataCompose(
() => useQuery(ArticleResource.getList.schema),
{
initialFixtures: [
{
endpoint: ArticleResource.getList,
args: [],
response: [{ id: 1, title: 'First', content: '' }],
},
],
resolverFixtures: [
{ endpoint: ArticleResource.getList.push, response: body => body },
],
},
);
expect(result.value).toHaveLength(1);

await controller.fetch(ArticleResource.getList.push, {
id: 2,
title: 'Second',
content: '',
});
await nextTick();
expect(result.value).toHaveLength(2);

cleanup();
});

API​

renderDataCompose(composable, options?)​

Una vez ejecutado el composable, se resuelve con los mismos controller, wrapper, cleanup() y allSettled() que mountDataClient(), además de:

MiembroDescripción
resultLo que devolvió el composable
waitForNextUpdate()Obsoleto. Se rinde en silencio tras 1 segundo, por lo que una prueba puede pasar mientras sigue suspendida. Usa await result cuando result sea una Promise, y await allSettled() después de un cambio