Pruebas unitarias con Vue

10/12

Lectura

Desarrollo de pruebas: Pruebas unitarias con Vue.

Ya que tenemos todo listo, empezamos a probar.

Vamos a eliminar todos los archivos que tenemos en nuestra carpeta de test/unit y crearemos un archivo que se llame request.spec.js (la parte de spec se pone por nomenclatura y buena práctica).

7.png

Dentro de este archivo importamos lo siguiente:

6.png
  • Nuestro servicio mock.

import { mockService } from '../../public/mockCall';

Después empezamos con un describe y de nombre le ponemos “Fetching data from mock service”.

Dentro de este describe le creamos el siguiente test:

1) Revisar que la promesa del servicio me está regresando los datos que quiero

Para esto mandaremos a llamar a mockService y utilizaremos dos matchers de Jest:

  • expect.assertions()

    • Este matcher recibe un número que significará el número de validaciones que se van a hacer dentro de esta prueba.

    • Cuando estemos trabajando con código asíncrono, debemos poder ver si se está llamando o no a nuestra validación.

  • expect(Promise.resolve(promesa)).resolves

    • Regresa el valor que salió del resolve_ _de la promesa.

Con estos matchers juntos probaremos que nuestra promesa nos regresa los datos que queremos. Nos hace falta algo más para probar estos datos y son los datos en sí, así que vamos a ir a nuestro archivo de mockCall.js y vamos a copiar el valor de la _variable pokemon _y vamos a pegarlo en otra entre nuestro describe y nuestro test.

Nos debe de quedar algo así:

5.png

Repasemos lo que hicimos.

Creamos una prueba que nos valida si la petición mock nos regresa los datos que queremos; para lograrlo, pusimos en una variable los datos que vamos a comparar y usamos el matcher .resolves para checar el resultado de la promesa.


Ya hemos probado la parte más difícil, solamente hace falta hacer las validaciones de las demás funcionalidades que tenemos.

Seguimos con la función de setData().

Como en esta función nos estamos metiendo con data(), también hay que validar data().


Creamos otro archivo llamado dataTest.spec.js en la misma carpeta donde esta el anterior archivo.

En esta carpeta vamos a importar shallowMount del módulo de @vue/test-utils.

Esta función, nos permite montar un componente sin cargar a los componentes hijos que tenga. Su contraparte es mount, que veremos más adelante.

Después de hacer esto, creamos un wrapper del componente para usarlo dentro de nuestras pruebas.

Hay que recordar que estamos haciendo pruebas unitarias, asi que vamos a probar el ¿QUÉ?

data() contiene varias propiedades, las cuales vamos a probar que existan y que sean del tipo que nosotros queremos. Quedando nuestro código de la siguiente manera:

describe(‘Testing integrity of data() properties’, () => {

    test('should have name property', () => {

        expect(wrapper.vm.$data).toHaveProperty('name')

        expect(typeof wrapper.vm.name).toBe('string')

    })

    test('should have image property', () => {

        expect(wrapper.vm.$data).toHaveProperty('image')

        expect(typeof wrapper.vm.image).toBe('string')

    })

    test('should have type property', () => {

        expect(wrapper.vm.$data).toHaveProperty('type')

        expect(typeof wrapper.vm.type).toBe('string')

    })

    test('should have weight property', () => {

        expect(wrapper.vm.$data).toHaveProperty('weight')

        expect(typeof wrapper.vm.weight).toBe('number')

    })

    test('should have height property', () => {

        expect(wrapper.vm.$data).toHaveProperty('height')

        expect(typeof wrapper.vm.height).toBe('number')

    })

    test('should have abilities property', () => {

        expect(wrapper.vm.$data).toHaveProperty('abilities')

        expect(Array.isArray(wrapper.vm.abilities)).toBeTruthy()

    })    

})

Si te das cuenta, en el último assert utilizamos la función Array.isArray() para que nos dé un valor booleano que podamos usar con el matcher toBeTruthy().

Lo último que vamos a probar es que todos nuestros elementos se estén renderizando de la manera correcta.

Añadimos un nuevo archivo llamado renderTest.spec.js y empezamos importando la función mount que mencionamos anteriormente, la cual nos permite renderizar nuestros componentes hijos.

Empezaremos probando que los elementos del padre se renderizan.

Estos son:

  • 2 section

  • 1 img

  • 2 p

  • 1 button

  • 1 ul

  • 1 componente poke-stats

Los elementos de la lista, al ser dinámicos, deberemos probarlos con las pruebas de integración.

Para hacer lo anterior, utilizaremos la función exist() e is para corroborar el tipo.

Quedando de la siguiente manera:

describe('Testing the correct rendering of elements', () => {



    const wrapper = mount(App)



    test('should render 2 sections', () => {

        expect(wrapper.find('.app-pokemon-main').exists()).toBeTruthy();

        expect(wrapper.find('.app-pokemon-main').is('section')).toBeTruthy();

        expect(wrapper.find('.app-pokemon-stats').exists()).toBeTruthy();

        expect(wrapper.find('.app-pokemon-stats').is('section')).toBeTruthy();

    })



    test('should render 1 img', () => {

        expect(wrapper.find('img').exists()).toBeTruthy();

        expect(wrapper.find('img').is('img')).toBeTruthy();

    })



    test('should render 2 p', () => {

        expect(wrapper.find('.pokemon-name').exists()).toBeTruthy();

        expect(wrapper.find('.pokemon-name').is('p')).toBeTruthy();

        expect(wrapper.find('#abilities').exists()).toBeTruthy();

        expect(wrapper.find('#abilities').is('p')).toBeTruthy();

    })



    test('should render 1 button', () => {

        expect(wrapper.find('button').exists()).toBeTruthy();

        expect(wrapper.find('button').is('button')).toBeTruthy();

    })



    test('should render 1 ul', () => {

        expect(wrapper.find('ul').exists()).toBeTruthy();

        expect(wrapper.find('ul').is('ul')).toBeTruthy();

    })



    test('should render 1 ul', () => {

        expect(wrapper.find('.component').exists()).toBeTruthy();

    })

    

})

Te preguntarás .component, ¿qué es eso?.

Normalmente cuando hacemos pruebas, tratamos de mantenerlas lo más modular posible, es decir, no probamos los hijos en los padres. En el caso de Vue, al no crear una etiqueta de nuestro componente, nos crea esta etiqueta con esa clase.

Lo recomendable es no probar los hijos y si los probamos, deberiamos probar las funcionalidades internas.

Si quieres ver como se ve esto, puedes probar poner un console.log(wrapper.html()) dentro de este describe para que veas cómo es que se renderiza todo.


Con eso concluimos nuestras pruebas unitarias. ¿Qué es lo que probarías de inmediato en tus proyectos? Ponlo en los comentarios. En la siguiente clase aprenderemos a implementar Pruebas de Integración.

Aportes 11

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

No entendí la clase, lo sentí incompleto.

Vale, a esta clase le hizo falta explicar algunas cosas, y siento que se pasó algunas otras cosas por alto, por ejemplo no se pusieron los código completos, ni se mostraron las partes de los import de las funciones mount pero creo que lo logré entender

Algo IMPORTANTE

Actualmente la función .is() está deprecada, en su lugar hay que usar element.tagName, por lo que nuestras validaciones deberían quedar así de ahora en adelante:

expect(wrapper.find('img').element.tagName.toLowerCase() == 'img').toBeTruthy();

Se usa al final toLoweCase porque tagName devuelve los tags en mayúscula, más información aquí:

https://vue-test-utils.vuejs.org/api/wrapper/is.html

Este capítulo es demasiado importante y no se logró explicar del todo. Tomé este curso por este módulo. Sugiero que graben este curso de nuevo con el mismo profesor.

En la actualidad, las pruebas unitarias son extremadamente importantes para conseguir un puesto de desarrollador. Es un excluyente.

Cambien todos los .is por:

expect(wrapper.find('.app-pokemon-main').element.tagName).toBe('SECTION')
expect(wrapper.find('.app-pokemon-stats').element.tagName).toBe('SECTION')
expect(wrapper.find('img').element.tagName).toBe('IMG');
expect(wrapper.find('.pokemon-name').element.tagName).toBe('P');
expect(wrapper.find('#abilities').element.tagName).toBe('P');
expect(wrapper.find('button').element.tagName).toBe('BUTTON');
expect(wrapper.find('ul').element.tagName).toBe('UL');

Si verifican la función tagname devuelve en mayúscula, por eso pongan en mayúscula en el toBe.

Para aquellos que se preguntan como solucionar el error

[vue-test-utils]: is is deprecated and will be removed in the next major version. Use element.tagName instead.

En ves de is solo cambien en a ->

    expect(wrapper.find('.app-pokemon-main').element.tagName).toBe('SECTION')

Y les quedara algo así.

    test('should render 2 sections', () => {
        expect(wrapper.find('.app-pokemon-main').exists()).toBeTruthy()

        expect(wrapper.find('.app-pokemon-main').element.tagName).toBe('SECTION')

        expect(wrapper.find('.app-pokemon-stats').exists()).toBeTruthy()

        expect(wrapper.find('.app-pokemon-stats').element.tagName).toBe('SECTION')
    })

Aquí el código de la sección
dataTest.spect.js

import {shallowMount } from '@vue/test-utils';
import App from '@/App.vue';
const wrapper = shallowMount(App);

describe('Testing of the data() properties', ()=>{
    test('should have name property',()=>{
        //Se valida la propiedad name de data()
        expect(wrapper.vm.$data).toHaveProperty('name');
        //Se valida el tipo de dato de la propiedad name
        expect(typeof wrapper.vm.name).toBe('string');
    });
    test('should have image property',()=>{
        //Se valida la propiedad image de data()
        expect(wrapper.vm.$data).toHaveProperty('image');
        //Se valida el tipo de dato de la propiedad image
        expect(typeof wrapper.vm.image).toBe('string');
    });
    test('should have type property',()=>{
        //Se valida la propiedad type de data()
        expect(wrapper.vm.$data).toHaveProperty('type');
        //Se valida el tipo de dato de la propiedad type
        expect(typeof wrapper.vm.type).toBe('string');
    });
    test('should have weight property',()=>{
        //Se valida la propiedad weight de data()
        expect(wrapper.vm.$data).toHaveProperty('weight');
        //Se valida el tipo de dato de la propiedad weight
        expect(typeof wrapper.vm.weight).toBe('number');
    });
    test('should have height property',()=>{
        //Se valida la propiedad height de data()
        expect(wrapper.vm.$data).toHaveProperty('height');
        //Se valida el tipo de dato de la propiedad height
        expect(typeof wrapper.vm.height).toBe('number');
    })
    test('should have abilities property',()=>{
        //Se valida la propiedad abilities de data()
        expect(wrapper.vm.$data).toHaveProperty('abilities');
        //Se valida el tipo de dato de la propiedad abilities 
        //preguntando si es o no un array por medio de un booleano
        expect(Array.isArray(wrapper.vm.abilities)).toBeTruthy();
    })
})

renderTest.spec.js

import {mount} from '@vue/test-utils';
import App from '@/App';

const wrapper = mount(App);

describe('Testing the correct rendering of elements', ()=>{
    test('should render 2 sections',()=>{
        //comprueba que exista la clase y luego comprueba si el elemento es un section
        expect(wrapper.find('.app-pokemon-main').exists()).toBeTruthy();
        expect(wrapper.find('.app-pokemon-main').element.tagName.toLowerCase() == 'section').toBeTruthy();
        expect(wrapper.find('.app-pokemon-stats').exists()).toBeTruthy();
        expect(wrapper.find('.app-pokemon-stats').element.tagName.toLowerCase() == 'section').toBeTruthy();
    });
    test('should render 1 image',()=>{
        //comprueba que exista la clase y luego comprueba si el elemento es un section
        expect(wrapper.find('img').exists()).toBeTruthy();
        expect(wrapper.find('img').element.tagName.toLowerCase() == 'img').toBeTruthy();
    });
    test('should render 2 p',()=>{
        //comprueba que exista la clase y luego comprueba si el elemento es un section
        expect(wrapper.find('.pokemon-name').exists()).toBeTruthy();
        expect(wrapper.find('.pokemon-name').element.tagName.toLowerCase() == 'p').toBeTruthy();
        expect(wrapper.find('#abilities').exists()).toBeTruthy();
        expect(wrapper.find('#abilities').element.tagName.toLowerCase() == 'p').toBeTruthy();
    });
    test('should render 1 button', () => {

        expect(wrapper.find('button').exists()).toBeTruthy();

        expect(wrapper.find('button').element.tagName.toLowerCase() == 'button').toBeTruthy();

    });
    test('should render 1 ul', () => {

        expect(wrapper.find('ul').exists()).toBeTruthy();

        expect(wrapper.find('ul').element.tagName.toLowerCase() == 'ul').toBeTruthy();

    });
    test('should render 1 ul', () => {
        //Normalmente cuando hacemos pruebas, tratamos de mantenerlas lo más modular posible, 
        //es decir, no probamos los hijos en los padres. 
        //En el caso de Vue, al no crear una etiqueta de nuestro componente, 
        //nos crea esta etiqueta con esa clase.
        expect(wrapper.find('.component').exists()).toBeTruthy();

    });
   // console.log(wrapper.html())
})```

Me marca lo siguiente, considero que esto se puede actualizar para hacerlo con la forma que te recomiendan

  console.error node_modules/@vue/test-utils/dist/vue-test-utils.js:1735
    [vue-test-utils]: is is deprecated and will be removed in the next major version. Use element.tagName instead.

  console.error node_modules/@vue/test-utils/dist/vue-test-utils.js:1735
    [vue-test-utils]: is is deprecated and will be removed in the next major version. Use element.tagName instead.

  console.error node_modules/@vue/test-utils/dist/vue-test-utils.js:1735
    [vue-test-utils]: is is deprecated and will be removed in the next major version. Use element.tagName instead.

  console.error node_modules/@vue/test-utils/dist/vue-test-utils.js:1735
    [vue-test-utils]: is is deprecated and will be removed in the next major version. Use element.tagName instead.

  console.error node_modules/@vue/test-utils/dist/vue-test-utils.js:1735
    [vue-test-utils]: is is deprecated and will be removed in the next major version. Use element.tagName instead.

El set de pruebas hecho en dataTest.spec.js, en el entorno laboral se debe hacer con cada atributo del data, o solo fue hecho con fines educativos?

De la misma manera, lo hecho en el set de pruebas hecho en renderTest.spec.js??

Gracias por responder.

Lo que probaria de inmediato en mis proyectos son las properties y los requests

Poniendo cómo ejemplo lo siguiente:

test('should render 2 sections', () => {

        expect(wrapper.find('.app-pokemon-main').exists()).toBeTruthy();

        expect(wrapper.find('.app-pokemon-main').is('section')).toBeTruthy();

        expect(wrapper.find('.app-pokemon-stats').exists()).toBeTruthy();

        expect(wrapper.find('.app-pokemon-stats').is('section')).toBeTruthy();

    })

Si tuviera que validar 10 sections, se debe hacer de la misma manera o se puede usar un for ?

Este fué un ejemplo práctico, muy bueno no sabía de esto, excelente y me pongo a pensar, en un proyecto real y enorme, hacer esto es mucha “talacha”…