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.

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 como corredor de pruebas y Vue Test Utils 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.
La recomendación por defecto de la documentación de Vue 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.
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.
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.
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')
})
})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.
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.
¿Listo para aprobar tus entrevistas de Vue.js / Nuxt.js?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
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.
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 recomienda este enfoque para las pruebas a nivel de componente.
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.
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.
¿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.
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
mountsobreshallowMountsalvo que un componente hijo sea pesado o ya esté cubierto en otro lugar. - Esperar siempre con
awaitatrigger,setValueynextTickantes 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.mockpara 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 ystubActions: falsecuando se ejercita la lógica del store. - Consultar los elementos mediante atributos
data-testpara 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 para seguir construyendo habilidades listas para la entrevista.
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
Etiquetas
Compartir
Artículos relacionados

Composables Avanzados en Vue 3: Patrones Reutilizables y Preguntas de Entrevista 2026
Domina los composables avanzados de Vue 3 con patrones reutilizables, manejo de errores asincrono, inyeccion de dependencias y validacion de formularios. Incluye preguntas de entrevista tecnica actualizadas a 2026.

Vue 3 Pinia vs Vuex: Gestión de Estado Moderna y Preguntas de Entrevista 2026
Comparación detallada entre Pinia y Vuex: diseño de API, soporte TypeScript, Composition API, rendimiento, estrategias de migración y preguntas frecuentes en entrevistas Vue para 2026.

Nuxt 4 en 2026: nueva estructura de directorios y migración desde Nuxt 3
Guía completa de migración a Nuxt 4: estructura de directorios app/, capa singleton de obtención de datos, mejoras en TypeScript e instrucciones paso a paso con ejemplos de código.