La librería que incluye Vue para testear las aplicaciones el vue-test_utils que proporciona herramientas para montar e interactuar con componentes. Para los tests unitarios usaremos Jest.
Tras instalar la librería con npm (será una dependencia de desarrollo) indicaremos al linter que vamos a usar jest para que no genere advertencias al usar sus comandos para lo que modificaremos el fichero .eslintrc.js y añadiremos al apartado env una línea indicando que vamos a usar jest. Dicho apartado quedará:
env: {
node: true,
jest: true,
},
Si usamos librerías como Vue-Material o Vuetify y debemos importar ficheros .css de las mismas en nuestros componentes es posible que falle Jest a la hora de pasar los test y nos dará un error de que no puede procesar el fichero porque no es Javascript. Podemos solucionarlo instalando para desarrollo el paquete identuty-obj-proxy y añadiendo una entrada para moduleNameMapper al fichero de configuración de Jest jest.config.js que quedará:
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
moduleNameMapper: {
"\\.(css|less|scss|sass)$": "identity-obj-proxy"
},
}
(fuente: https://stackoverflow.com/questions/46177148/how-to-exclude-css-module-files-from-jest-test-suites)
A la hora de crear el proyecto no escogeremos preset sino que seleccionaremos manualmente las características a instalar y marcaremos la de tests unitarios con Jest que es la librería que usamos en el bloque de Javascript.
Para ejecutar los tests ejecutaremos en la terminal
npm run test:unit
El projecto está configurado para ejecutar los ficheros de pruebas cuyo nombre acabe por .spec.js. Por defecto se guardan en la carpeta /tests.
En primer lugar vamos a analizar el test que hay hecho en @/tests/exemple.spec.js para testear el componente HelloWorld.vue:
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
})
Lo primero que hay que hacer es importar el plugin de tests de vue y el componente a testear. Dentro de la prueba se monta el componente (shallowMount
) y se le pasan las props que necesite (msg). A esta función se le pasa un componente y devuelve la instancia de Vue creada para él y su nodo del DOM.
Como segundo parámetro se le puede pasar un objeto con opciones a montar en el componente (por ejemplo un data que sustituirá al del componente) o, como en el ejemplo anterior, los parámetros que se le pasan al componente (en propsData).
Además de shallowMount
podemos usar (si lo importamos) el método mount
que hace lo mismo pero también renderiza los subcomponentes que tenga el componente.
Por último se comprueba que el texto renderizado por el template del componente incluye el mensaje pasado. La variable wrapper es el nodo DOM raíz del componente y podemos obtener su textContent (.text()
), su innerHTML (.html()
), sus atributos (.attributes()
, y para acceder a uno, por ejemplo la id haríamos .attributes().id
), sus clases (.classes()
), etc.Podemos ver todos sus métodos en la documentación oficial de Vue test utils.
También podríamos haber hecho la siguiente comprobación:
expect(wrapper.html()).toMatch('<h1>'+msg+'</h1>')
o bien comprobar directamente el valor de prop:
expect(wrapper.props().msg).toBe(msg)
El componente que vamos a probar es:
<template>
<div>
<h1>Testing dom attributes</h1>
<a href="https://google.com" class="link" style="color:green">Google</a> </div>
</template>
<script>
export default {};
</script>
Y el test es:
import App from '../src/App.vue'
import { shallowMount } from '@vue/test-utils';
describe('Testing dom attributes', () => {
it('checks href to google ', () => {
const wrapper = shallowMount(App);
const a = wrapper.find('a'); //finds an `a` element
expect(a.attributes().href).toBe('https://google.com')
})
})
Si lo que queremos comprobar son las clases, estas tienen su propio método:
describe('Testing class', () => {
it('checks the class to be link', () => {
const wrapper = shallowMount(App);
const a = wrapper.find('a'); //finds an `a` element
expect(a.classes()).toContain('link')
})
})
Y lo mismo ocurre para comprobar un estilo:
describe('Testing style', () => {
it('checks the inline style color to be green', () => {
const wrapper = shallowMount(App);
const a = wrapper.find('a'); //finds an `a` element
expect(a.style.color).toBe('green')
})
})
El componente que vamos a probar es:
<template>
<div>
<h1></h1> <button @click="changeTitle">Change title</button> </div>
</template>
<script>
export default {
data: function() {
return {
title: "Hello" };
},
methods: {
changeTitle() {
this.title = "Hi"; }
}
};
</script>
Y el test es:
import { shallowMount } from '@vue/test-utils';
import Post from '../src/components/Welcome.vue'
describe('Testing Component Methods', () => {
const wrapper = shallowMount(Post);
it('correctly updates the title when changeTitle is called', () => {
expect(wrapper.vm.title).toBe('Hello'); //initial title Hello
wrapper.vm.changeTitle(); // calling component method
expect(wrapper.vm.title).toBe('Hi'); // title updates to Hi
})
})
El componente que vamos a probar es:
<template>
<div>
<h1>8</h1>
<button @click="increment">Increment</button> </div>
</template>
<script>
export default {
data: function() {
return {
count:0
};
},
methods: {
increment() {
this.count++;
}
}
};
</script>
Y el test es:
import { shallowMount } from '@vue/test-utils';
import Post from '../src/components/Counter.vue'
describe('Testing native dom events', () => {
const wrapper = shallowMount(Post);
it('calls increment method when button is clicked', () => {
const increment = jest.fn(); // mock function
// updating method with mock function
wrapper.setMethods({ increment });
//find the button and trigger click event
wrapper.find('button').trigger('click');
expect(increment).toBeCalled();
})
})
Fuente: Testing Dom events in Vue.js using Jest and vue-test-utils. Sai gowtham
Dado que Vue realiza las actualizaciones de DOM de forma asíncrona, las comprobaciones sobre las actualizaciones de DOM resultantes del cambio de estado, deberán realizarse en un callback Vue.nextTick
.
it('button click should increment the count text', async () => {
expect(wrapper.text()).toContain('0')
const button = wrapper.find('button')
button.trigger('click')
await Vue.nextTick()
expect(wrapper.text()).toContain('1')
})
En muchos casos hacemos peticiones asíncronas, como peticiones a una API. Podéis obtener información en:
En primer lugar vamos a testear que la propiedad ‘done’ tiene el valor que se le pasa y que cambia al llamar a la función ‘toogleDone’:
import { shallowMount } from '@vue/test-utils'
import Usuario from '@/components/Usuario.vue'
describe('componente Usuario.vue', () => {
it('debe cambiar el valor a true', () => {
/// Crea una instancia del componente
const wrapper = shallowMount(Usuario);
/// Evalúa que el valor por defecto sea "false"
expect(wrapper.vm.usuarioActivo).toBe(false);
/// Ejecuta el metodo que cambia el valor de la variable a "true"
wrapper.vm.activarUsuario();
/// Evalúa que el nuevo valor usuarioActivo sea "true"
expect(wrapper.vm.usuarioActivo).toBe(true);
})
})
Normalmente nuestros componentes usaran Vuex para:
Es sencillo porque sólo son llamadas Javascript. Ejemplo:
// store.js
...
mutations: {
addPost(state, post) {
state.posts.push(post);
},
}
...
// store.spec.js
import { mutations } from "@/store/index.js"
describe("addPost", () => {
it("adds a post to the state", () => {
const post = { id: 1, title: "Primer post" }
const state = {
posts: [],
}
mutations.addPost(state, post)
expect(state).toEqual({
posts: [ { id: 1, title: 'Primer post' } ]
})
})
})
Podéis encontrar un completo ejemplo de cómo testear una aplicación ToDo en Adictos al trabajo - Testing en componentes de Vue.js.
Podéis encontrar ejemplos más completos en muchas páginas, como:
Fuentes:
No comprueban un componente sino un workflow completo, por ejemplo, que el usuario introduce algo como nombre de nueva tarea, pulsa enviar y se añade la tarea a la lista.
Al pasar los tests arranca un servidor de tests (selenium) y un navegador donde hace las pruebas y luego los cierra.