# Testing en Vue 3 en 2026: Vitest, Vue Test Utils y preguntas de entrevista > Guía práctica de testing en Vue para 2026: configurar Vitest, montar componentes con Vue Test Utils, probar composables y stores de Pinia, simular APIs, medir cobertura y responder las preguntas de entrevista que hacen los equipos de contratación. - Published: 2026-06-21 - Updated: 2026-07-06 - Author: SharpSkill - Tags: vue, testing, vitest, vue-test-utils, interview - Reading time: 10 min --- El testing en Vue marca la diferencia entre refactorizar con confianza y avanzar a ciegas. En 2026 el conjunto de herramientas se consolidó en torno a dos librerías: [Vitest](https://vitest.dev/guide/) como corredor de pruebas y [Vue Test Utils](https://test-utils.vuejs.org/) para montar componentes. Esta guía recorre la configuración del stack, cómo probar componentes y composables, cómo simular dependencias y cómo responder las preguntas de entrevista sobre testing en Vue que realmente formulan los equipos de contratación. > **El stack moderno de testing en Vue en 2026** > > La recomendación por defecto de la [documentación de Vue](https://vuejs.org/guide/scaling-up/testing.html) es Vitest para pruebas unitarias y de componentes, Vue Test Utils como API de montaje de bajo nivel, y Playwright o Cypress para la cobertura de extremo a extremo. Vitest reutiliza la misma configuración de Vite que la aplicación, así que las pruebas atraviesan idéntica cadena de transformación con recarga en caliente casi instantánea. ## Configurar Vitest en un proyecto Vue 3 Vitest comparte la cadena de Vite, lo que significa que un único archivo de configuración gobierna tanto el servidor de desarrollo como el corredor de pruebas. La opción `environment` debe fijarse en `jsdom` o `happy-dom` para que las pruebas de componentes dispongan de un DOM donde renderizar. El flag `globals: true` expone `describe`, `it` y `expect` sin necesidad de importarlos en cada archivo. ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' import { fileURLToPath } from 'node:url' export default defineConfig({ plugins: [vue()], test: { // jsdom provides document/window so components can mount environment: 'jsdom', // expose describe/it/expect globally globals: true, // run this before each test file (matchers, cleanup) setupFiles: ['./tests/setup.ts'], // only pick up *.spec.ts and *.test.ts files include: ['**/*.{test,spec}.ts'], }, resolve: { alias: { // mirror the @ alias used across the app '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, }) ``` Con Vitest 3 la configuración anterior funciona de inmediato tras instalar `vitest`, `@vue/test-utils`, `@vitejs/plugin-vue` y `jsdom`. Como el alias replica la configuración de la aplicación, importaciones como `@/composables/useCart` se resuelven de forma idéntica en las pruebas y en producción. ## Montar componentes con Vue Test Utils Vue Test Utils ofrece dos puntos de entrada: `mount`, que renderiza el árbol de componentes completo, y `shallowMount`, que reemplaza los componentes hijos por stubs. Para la mayoría de las pruebas unitarias se prefiere `mount`, porque ejercita el comportamiento real de renderizado. El `wrapper` devuelto aporta ayudantes de consulta como `find`, `get` y `text` para verificar la salida renderizada. ```typescript // PriceTag.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import PriceTag from '@/components/PriceTag.vue' describe('PriceTag', () => { it('formats the amount as currency', () => { const wrapper = mount(PriceTag, { // props are passed through the mounting options props: { amount: 4200, currency: 'EUR' }, }) // get() throws if the selector is missing, unlike find() expect(wrapper.get('[data-test="price"]').text()).toBe('€42.00') }) it('applies a discount class when on sale', () => { const wrapper = mount(PriceTag, { props: { amount: 4200, currency: 'EUR', onSale: true }, }) // classes() returns the array of applied CSS classes expect(wrapper.get('[data-test="price"]').classes()).toContain('price--sale') }) }) ``` Apuntar a los elementos mediante atributos `data-test` en lugar de clases CSS mantiene las pruebas resistentes: un cambio de estilo en `.price--sale` no romperá el selector, solo lo hará un cambio genuino de comportamiento. Este desacoplamiento es un tema recurrente en el testing mantenible de Vue. ## Probar eventos de usuario y eventos emitidos Los componentes interactivos necesitan verificar qué ocurre tras un clic o una entrada de texto. Vue Test Utils devuelve una promesa desde `trigger`, y esperarla con `await` vacía la cola de reactividad de Vue para que el DOM refleje la actualización. El ayudante `emitted` registra cada evento personalizado que disparó el componente, y así se verifican los contratos entre padre e hijo. ```typescript // SearchBar.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import SearchBar from '@/components/SearchBar.vue' describe('SearchBar', () => { it('emits search with the typed query', async () => { const wrapper = mount(SearchBar) // setValue writes to the input and fires an input event await wrapper.get('input').setValue('vitest') // awaiting trigger flushes the reactivity queue before assertions await wrapper.get('[data-test="submit"]').trigger('click') // emitted() returns a record of every event and its payloads const events = wrapper.emitted('search') expect(events).toHaveLength(1) // events[0] is the first emission, [0] its first argument expect(events![0][0]).toBe('vitest') }) }) ``` > **Siempre espera las actualizaciones del DOM** > > Vue agrupa las actualizaciones reactivas de forma asíncrona. Olvidar el `await` en una llamada a `trigger` o `setValue` es la causa más habitual de pruebas inestables en Vue: las verificaciones se ejecutan antes de que el DOM vuelva a renderizar, por lo que leen una salida obsoleta. Cuando un cambio de estado ocurre fuera de un ayudante de eventos, hay que importar `nextTick` desde `vue` y hacer `await nextTick()` antes de verificar. ## Probar composables de Vue de forma aislada Los composables que solo emplean APIs de reactividad (`ref`, `computed`, `watch`) no requieren ningún componente: son funciones planas que devuelven estado reactivo, así que basta con invocarlas directamente y verificar sobre `.value`. Esta es la manera más rápida y enfocada de cubrir la lógica de negocio, y combina bien con los patrones descritos en [composables avanzados de Vue 3](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns). ```typescript // useCart.spec.ts import { describe, it, expect } from 'vitest' import { useCart } from '@/composables/useCart' describe('useCart', () => { it('tracks total price as items change', () => { // composables using only ref/computed run without mounting const { items, total, addItem } = useCart() expect(total.value).toBe(0) addItem({ id: 1, price: 1500, qty: 2 }) // computed total recalculates synchronously on state change expect(total.value).toBe(3000) addItem({ id: 2, price: 500, qty: 1 }) expect(items.value).toHaveLength(2) expect(total.value).toBe(3500) }) }) ``` Los composables que dependen de hooks del ciclo de vida como `onMounted` son la excepción: deben ejecutarse dentro de una instancia de componente. El truco habitual es un componente auxiliar diminuto que llama al composable en `setup` y expone el resultado, y luego se monta ese auxiliar con Vue Test Utils. ## Simular llamadas a la API con vi.mock Las peticiones de red reales vuelven las pruebas lentas y no deterministas. Vitest reemplaza módulos con `vi.mock` y crea funciones espía con `vi.fn`. El patrón siguiente simula el módulo que envuelve a `fetch`, de modo que el composable bajo prueba recibe datos controlados sin llegar jamás a un servidor. ```typescript // useProducts.spec.ts import { describe, it, expect, vi, beforeEach } from 'vitest' import { useProducts } from '@/composables/useProducts' import { getProducts } from '@/api/products' // replace the whole module with mocked exports vi.mock('@/api/products', () => ({ getProducts: vi.fn(), })) describe('useProducts', () => { beforeEach(() => { // reset call history between tests to avoid leakage vi.mocked(getProducts).mockReset() }) it('exposes fetched products and clears loading', async () => { // define what the mocked API returns for this test vi.mocked(getProducts).mockResolvedValue([ { id: 1, name: 'Keyboard' }, ]) const { products, loading, load } = useProducts() await load() expect(getProducts).toHaveBeenCalledOnce() expect(loading.value).toBe(false) expect(products.value[0].name).toBe('Keyboard') }) }) ``` Simular en la frontera del módulo deja intacta la lógica interna del composable a la vez que otorga a cada prueba control total sobre la respuesta de la API, incluidos los caminos de error. Usar `mockRejectedValue` en su lugar permite que una sola prueba verifique que las fallas establecen un estado de error y detienen el spinner. ## Probar stores de Pinia en 2026 Los stores concentran el estado compartido entre componentes, y el paquete oficial `@pinia/testing` los hace fáciles de probar. `createTestingPinia` convierte cada acción en un stub por defecto, de modo que una prueba de componente puede verificar que una acción fue despachada sin ejecutar sus efectos secundarios. La [guía de testing de Pinia](https://pinia.vuejs.org/cookbook/testing.html) recomienda este enfoque para las pruebas a nivel de componente. ```typescript // CheckoutButton.spec.ts import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import { createTestingPinia } from '@pinia/testing' import CheckoutButton from '@/components/CheckoutButton.vue' import { useCartStore } from '@/stores/cart' describe('CheckoutButton', () => { it('dispatches checkout on click', async () => { const wrapper = mount(CheckoutButton, { global: { plugins: [ // vi.fn spies on actions instead of executing them createTestingPinia({ createSpy: vi.fn }), ], }, }) const store = useCartStore() await wrapper.get('[data-test="checkout"]').trigger('click') // the action is stubbed, so only the dispatch is asserted expect(store.checkout).toHaveBeenCalledOnce() }) }) ``` Para probar la lógica del store en sí y no la integración con el componente, se pasa `stubActions: false` para que las acciones se ejecuten con normalidad, y luego se verifica el estado resultante. Esta separación entre pruebas de componente con acciones simuladas y pruebas de store con acciones reales mantiene cada suite enfocada en una única responsabilidad. ## Ejecutar pruebas en modo watch y medir la cobertura Vitest incluye un modo watch que vuelve a ejecutar únicamente las pruebas afectadas por un archivo guardado, lo que acorta el ciclo de retroalimentación durante el desarrollo a una fracción de segundo. El reporte de cobertura viene incorporado a través del proveedor `@vitest/coverage-v8` y se integra sin fricción en una cadena de integración continua, donde una prueba fallida o una regresión de cobertura puede bloquear una fusión. ```typescript // vitest.config.ts (coverage section) export default defineConfig({ test: { coverage: { // v8 is the fastest provider and needs no instrumentation step provider: 'v8', reporter: ['text', 'html', 'lcov'], // fail CI when a threshold drops below the target thresholds: { statements: 80, branches: 75, functions: 80, }, // exclude generated and config files from the report exclude: ['**/*.config.ts', '**/types/**'], }, }, }) ``` Ejecutar `vitest` sin más inicia el modo watch en local, mientras que `vitest run --coverage` realiza una única pasada apropiada para CI. Definir umbrales explícitos convierte la cobertura de una métrica cosmética en una barrera de calidad exigida: un pull request que baja la cobertura de ramas por debajo del 75 por ciento falla de forma automática, lo que empuja a quienes contribuyen a probar los caminos que agregan y no solo el escenario feliz. ## Preguntas frecuentes de entrevista sobre testing en Vue con respuestas Las preguntas sobre testing aparecen en la mayoría de las entrevistas senior de Vue porque revelan cómo razona una persona candidata acerca de la mantenibilidad. Las respuestas siguientes cubren los conceptos que surgen con más frecuencia, y el conjunto completo se practica en el [módulo de entrevista sobre testing en Vue](/technologies/vue-nuxt/interview-questions/vue-testing). **¿Cuándo conviene usar `shallowMount` en lugar de `mount`?** Se recurre a `shallowMount` cuando un componente renderiza hijos pesados o ya probados y la prueba solo se interesa por la lógica propia del padre. Reemplazar los hijos por stubs aísla la unidad y acelera el renderizado, a cambio de no verificar la integración real entre componentes. **¿Cómo se manejan las actualizaciones asíncronas en Vue Test Utils?** Vue aplica los cambios reactivos en el siguiente tick. Ayudantes de eventos como `trigger` y `setValue` devuelven promesas que se resuelven después de que el DOM se actualiza, así que basta con esperarlas. Para el estado modificado fuera de esos ayudantes, `await nextTick()` fuerza el vaciado de la cola antes de que se ejecuten las verificaciones. **¿Cuál es la diferencia entre `find` y `get`?** `find` devuelve un wrapper vacío cuando ningún elemento coincide y nunca lanza una excepción, lo que encaja con verificaciones como `expect(wrapper.find('.error').exists()).toBe(false)`. `get` lanza una excepción de inmediato ante un elemento ausente, lo que lo vuelve la mejor opción cuando se espera que el elemento exista y su ausencia constituye una falla genuina. **¿Por qué preferir atributos `data-test` frente a selectores de clase?** Los nombres de clase cambian con el estilo y la estructura, así que las pruebas que los consultan se rompen ante ediciones cosméticas. Un gancho `data-test` dedicado expresa la intención de la prueba de forma explícita y sobrevive a las refactorizaciones, reduciendo los falsos negativos. **¿Cómo se prueba un composable que depende de `onMounted`?** Los hooks del ciclo de vida solo se disparan dentro de una instancia de componente, por lo que el composable no puede invocarse directamente. La técnica estándar es un componente auxiliar desechable que invoca el composable en `setup` y expone su valor de retorno, y luego se monta ese auxiliar con Vue Test Utils y se verifica a través del wrapper. Así se mantiene intacto el cableado de reactividad y ciclo de vida mientras se aísla la lógica bajo prueba. Se recopilan más preguntas conceptuales y de código en la guía de [preguntas esenciales de entrevista de Vue.js](/blog/vue-nuxt/essential-vuejs-interview-questions). ## Conclusión Una configuración fiable de testing en Vue en 2026 se reduce a unas pocas decisiones deliberadas: - Ejecutar Vitest con `environment: 'jsdom'` y el plugin de Vue para que las pruebas compartan exactamente la cadena de transformación de Vite de la aplicación. - Preferir `mount` sobre `shallowMount` salvo que un componente hijo sea pesado o ya esté cubierto en otro lugar. - Esperar siempre con `await` a `trigger`, `setValue` y `nextTick` antes de verificar, para eliminar las pruebas inestables dependientes del tiempo. - Probar los composables que solo usan APIs de reactividad invocándolos directamente y verificando sobre `.value`. - Simular en la frontera del módulo con `vi.mock` para que el comportamiento de red sea determinista y los caminos de error se puedan probar. - Usar `createTestingPinia({ createSpy: vi.fn })` para las pruebas de componente y `stubActions: false` cuando se ejercita la lógica del store. - Consultar los elementos mediante atributos `data-test` para que las pruebas se rompan solo ante cambios reales de comportamiento y nunca por el estilo. Dominar estos patrones convierte el testing en una herramienta de diseño en vez de una tarea tediosa, atrapando regresiones antes de que lleguen a producción. Se puede explorar la [ruta de aprendizaje completa de Vue y Nuxt](/technologies/vue-nuxt) para seguir construyendo habilidades listas para la entrevista. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/vue-nuxt/vue-3-testing-vitest-vue-test-utils