Pruebas unitarias de componentes
@data-client/vue/test monta componentes con DataClientPlugin,
un límite <Suspense> y Fixtures, de modo que las pruebas pueden comprobar lo que renderiza
un componente sin un ciclo de fetch de red. Para probar composables por separado, consulta
Pruebas unitarias de composables.
Configuración
Las utilidades de prueba están construidas sobre Vue Test Utils, así que instálalo junto con tu ejecutor de pruebas. Jest necesita el entorno jsdom.
- NPM
- Yarn
- pnpm
- esm.sh
yarn add --dev @vue/test-utils jest-environment-jsdom
npm install --saveDev @vue/test-utils jest-environment-jsdom
pnpm add -D @vue/test-utils jest-environment-jsdom
<script type="module">
import * from 'https://esm.sh/@vue/test-utils?dev';
import * from 'https://esm.sh/jest-environment-jsdom?dev';
</script>
mountDataClient()
initialFixtures
llenan el store antes del primer renderizado, de modo que un componente que hace await de
useSuspense() renderice sus datos de inmediato.
import { mountDataClient } from '@data-client/vue/test';
import { useSuspense } from '@data-client/vue';
import { defineComponent, h } from 'vue';
import { ArticleResource } from './resources';
import { flushUntil } from './flushUntil';
const ArticleDetail = defineComponent({
props: { id: { type: Number, required: true } },
async setup(props) {
// a getter so the fetch follows props.id
const article = await useSuspense(ArticleResource.get, () => ({
id: props.id,
}));
return () => h('h3', article.value.title);
},
});
it('renders the article', async () => {
const { wrapper, cleanup } = mountDataClient(ArticleDetail, {
props: { id: 5 },
initialFixtures: [
{
endpoint: ArticleResource.get,
args: [{ id: 5 }],
response: { id: 5, title: 'hi ho', content: 'whatever' },
},
],
});
await flushUntil(() => wrapper.find('h3').exists());
expect(wrapper.find('h3').text()).toBe('hi ho');
cleanup();
});
Llama siempre a cleanup() al final de una prueba; desmonta la aplicación y sus managers.
Esperar a los renderizados
El setup() asíncrono y Suspense se resuelven a lo largo de varias microtareas y ticks, así que espera a lo que
la prueba necesita en lugar de un número fijo de ticks:
import { nextTick } from 'vue';
export async function flushUntil(predicate: () => boolean, tries = 100) {
for (let i = 0; i < tries; i++) {
if (predicate()) return;
await nextTick();
await new Promise(resolve => setTimeout(resolve, 0));
}
throw new Error('flushUntil: condition never became true');
}
Estado de carga
Mientras un componente está suspendido, mountDataClient() renderiza un elemento con
data-testid="suspense-fallback". Usa resolverFixtures para responder a los fetch que el store
aún no tiene:
it('shows the fallback until the fetch resolves', async () => {
const { wrapper, cleanup } = mountDataClient(ArticleDetail, {
props: { id: 5 },
resolverFixtures: [
{
endpoint: ArticleResource.get,
response: ({ id }) => ({ id, title: `Article ${id}`, content: '' }),
},
],
});
expect(wrapper.find('[data-testid="suspense-fallback"]').exists()).toBe(true);
await flushUntil(() => wrapper.find('h3').exists());
expect(wrapper.find('h3').text()).toBe('Article 5');
cleanup();
});
El response de un interceptor recibe los argumentos del endpoint, así que un solo fixture puede responder a cualquier
id. Agrega error: true a un fixture para que sea rechazado; consulta Fixtures para ver todas las
formas de fixture.
Cambiar props
Pasa un objeto reactive() como props y luego modifica sus miembros para cambiar las props del componente:
import { reactive } from 'vue';
it('fetches the new article when id changes', async () => {
const props = reactive({ id: 1 });
const { wrapper, cleanup } = mountDataClient(ArticleDetail, {
props,
resolverFixtures: [
{
endpoint: ArticleResource.get,
response: ({ id }) => ({ id, title: `Article ${id}`, content: '' }),
},
],
});
await flushUntil(() => wrapper.text() === 'Article 1');
props.id = 2;
await flushUntil(() => wrapper.text() === 'Article 2');
cleanup();
});
Un setup() asíncrono se ejecuta una vez por instancia de componente, así que pasa los argumentos derivados de props como un getter
(() => ({ id: props.id })) o con computed(). Un { id: props.id } simple se lee una sola vez y no
seguirá los cambios de las props.
Mutaciones
El controller devuelto es el Controller de la aplicación. Ejecuta con él mutaciones o
setResponse() y luego espera a que el componente se actualice:
it('re-renders when the store changes', async () => {
const { wrapper, controller, cleanup } = mountDataClient(ArticleDetail, {
props: { id: 5 },
initialFixtures: [
{
endpoint: ArticleResource.get,
args: [{ id: 5 }],
response: { id: 5, title: 'hi ho', content: 'whatever' },
},
],
});
await flushUntil(() => wrapper.find('h3').exists());
controller.setResponse(
ArticleResource.get,
{ id: 5 },
{ id: 5, title: 'updated', content: 'whatever' },
);
await flushUntil(() => wrapper.find('h3').text() === 'updated');
cleanup();
});
API
mountDataClient(component, options?)
Devuelve
| Miembro | Descripción |
|---|---|
wrapper | VueWrapper de Vue Test Utils del árbol montado |
controller | El Controller de la aplicación |
app | La instancia de la aplicación Vue |
cleanup() | Desmonta y detiene los managers |
allSettled() | Se resuelve cuando termina cada fetch en curso, incluidos los que un cambio de props acaba de iniciar, y el componente se ha vuelto a renderizar |
Options
mountDataClient() y renderDataCompose() aceptan las mismas opciones.
interface RenderDataClientOptions<P = any> {
/** Props for the component; pass a reactive() object to change them */
props?: Reactive<P>;
/** Responses in the store before the first render */
initialFixtures?: readonly Fixture[];
/** Answer fetches made during the test */
resolverFixtures?: readonly (Fixture | Interceptor)[];
/** Initial `this` for interceptors */
getInitialInterceptorData?: () => any;
/** Replace the default NetworkManager and SubscriptionManager */
managers?: Manager[];
/** Replace the state built from initialFixtures */
initialState?: State<unknown>;
gcPolicy?: GCInterface;
/** Component rendered around the tested component (receives the same props) */
wrapper?: Component;
}
mockInitialState() construye initialState a partir de fixtures, para
montar con tu propia configuración de DataClientPlugin.