Tests E2E con Cypress en React

0
1635

Índice de contenidos

1. Introducción

Ya hemos hablado en adictos al trabajo previamente de Cypress y de los tests E2E, y no ha cambiado sustancialmente nuestra opinión, ni para lo que vale el producto. Te recomiendo echarle un ojo a este tutorial antes de seguir adelante, ya que sigue estando totalmente vigente.

Para dar al tutorial un enfoque totalmente práctico, lo que vamos a hacer es añadir tests E2E al código de ejemplo que creamos en el tutorial de inicio a React. El código definitivo está ya subido en master por si no nos apetece montarlo nosotros mismos desde cero.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Sistema Operativo: Windows 11
  • Entorno de desarrollo: Webstorm 2021.1
  • npm 6.14.12
  • node 14.18.1

3. Añadiendo Cypress a nuestro proyecto

Si me hiciste caso y le echaste un vistazo al tutorial previo de Cypress ya sabrás que para añadirlo a nuestro proyecto basta con ejecutar el comando npm install –save-dev cypress. Y por romper una lanza en favor de alguna tecnología de frontend, el anterior comando funciona y no da problemas. Hace lo que se espera.

Para validar que todo está correcto puedes ejecutar npx cypress run a ver si se ejecuta correctamente cypress, con los tests que trae por defecto.

4. En que situaciones tiene sentido añadir tests de Cypress y cuando no

Las funciones que escribimos deben estar testadas con tests unitarios siempre que sea posible, con lo que lo que nos aporta Cypress bajo mi punto de vista (tests E2E) es para comprobar que las acciones que hace un usuario acaban ejecutando el código que hemos programado y testado de forma unitaria, o que hemos inicializado un componente con un valor por defecto.

Vamos a suponer un ejemplo donde estamos filtrando un selector de ciudades en función de un campo país. Pues en este caso lo que podemos hacer es crear un test que cambia el país, y fijarnos que ahora el número de items de la lista de ciudades es 2, porque justamente en nuestro juego de datos el país seleccionado tiene 2 ciudades. En este caso no me pondría a hacer ninguna verificación en detalle de la funcionalidad que se ha ejecutado, ya que esa ya está probada en su tests unitario, sino unas verificaciones mínimas para comprobar que se ha ejecutado la función que se tiene que ejecutar.

No siempre es suficiente con probar las funciones de forma unitaria. Eso sería como si en un puzzle comprobamos que las piezas están bien creadas de forma individual. Que no tiene flecos, que el dibujo no está descolorido… Yo veo a Cypress como probar que las piezas encajan unas con otras para que el producto se comporte como queremos, en el ejemplo sería que el puzzle se completara y saliera la imagen final. Si solo hacemos tests unitarios, no tenemos ninguna prueba de que cuando haces click en cierta parte de la pantalla se abre otro componente con «x» título por ejemplo.

5. La odisea de las dependencias en el frontend (tomo 14 sección 25)

En el anterior tutorial de React estaba usando un componente para pintar la fecha que no permite que se escriba directamente la fecha con el teclado. Así que para probar lo que yo quería decidí cambiar ese componente por el KeyboardDatePicker de Material UI que como os podéis imaginar por su nombre, si admite input de teclado.

Ejecutamos «npm i @material-ui/pickers» tal y como se indica en el típico Get Started de Material UI.

Instalo también como comentan una librería de manejo de fechas «npm i @date-io/date-fns». Y ahora lo indican, pero en su día o no lo vi o no estaba, que solo funcionan con las v1, y no con las v2.

Pero lo que no estaba, y sigue a fecha de este tutorial sin estar, es que o añades «npm install @material-ui/core» o al arrancar el proyecto vas a tener problemas con un componente «xxxButton». Vamos que la documentación de la librería para empezar a usarla está distribuida entre la web del desarrollador y stackoverflow.

6. Algunos ejemplos de tests para nuestra aplicación

Pues vamos a hacer unas pruebas con nuestro componente FormWithConfirmation. Lo primero de todo es crearse un fichero spec, y en mi caso lo he puesto en la carpeta que te crea cypress cuando se instala al principio. La ruta en mi caso sería «cypress/integration/formWithConfirmation.spec.js»

Ya dentro del fichero lo primero es empezar llamando a la función describe que como primer parámetro acepta un string para poner un nombre descriptivo a este grupo de tests. En mi caso pongo el nombre del componente. El segundo parámetro sería una función. Justamente dentro de esta función voy a poner todos los tests que debe cumplir este componente y lo común a todos ellos.

Y lo común a todos ellos básicamente es que se navegue hacia el componente que estamos probando. Como nuestra aplicación de ejemplo es muy sencilla basta con ir a http://localhost:3000 como se muestra abajo.

	beforeEach(() => {

        cy.visit('http://localhost:3000')
    })
Ejemplo 1

Vamos a probar que nuestro componente de fecha, por defecto, tiene asignado el día de hoy, tal y como «se nos ha especificado en los requisitos por el product owner».

	it('should have today as initial value', () => {

        cy.get('input:first').should('have.value', format(new Date(), "dd MM yyyy"))
    })

Empezamos definiendo el test llamando a la función it, que como primer parámetro recibe un nombre descriptivo para el test, y como segundo parámetro la función donde definimos todo el test.

En nuestro caso simplemente navegamos hasta el primer input de la página, que es donde se introduce la fecha y comprobamos que el valor que tiene es justamente el del día de hoy.

Ejemplo 2

Como cuando estamos cambiando la fecha, se nos muestra un panel modal para confirmar los cambios, vamos a probar, que si cambiamos la fecha, primero nos sale el dialogo pidiendo confirmación, y en concreto en este caso vamos a descartar los cambios y comprobar que la variable que guarda la fecha no se ha actualizado. Nos podría quedar algo parecido a lo de abajo.

	it('Date is not changed when we dont confirm changes', () => {

        cy.get('input:first').clear().type('12 12 2023')

        cy.get('[role=dialog] h2').should('be.visible')
        cy.get('[role=dialog] h2').should('have.text', 'Are you sure about the changes?')

        cy.get('[role=dialog] button').should('have.length', 2)
        cy.get('[role=dialog] button').eq(0).click()

        // Somehow the value is changed in the DOM, so this can't be used
        // cy.get('input:first').should('have.value', format(new Date(), "dd MM yyyy"))

        cy.get('.TextWithLeftPadding').should('have.text', 'Frontend technologies will be as mature as backend technologies in ' + new Date().toDateString())
    })

Como precisamente hemos metido un componente de fecha que admite que le escribamos la fecha por teclado, utilizamos la función type para escribir un día cualquiera.

En las siguientes líneas vamos a comprobar que efectivamente, como hemos hecho un cambio, nos debería salir la ventana modal de confirmación de cambios, comprobamos el título de la ventana modal. También que debería haber además 2 botones y hacemos click en el primero de ellos, que es el de descartar los cambios.

Y aquí personalmente yo hubiera preferido que el componente de material-UI, aunque escribas con teclado, si el valor no se actualiza, volviera a pintar el valor de la variable que representa, pero no funciona de esta manera, de ahí el código comentado.

Como en esta app de ejemplo estamos sacando el valor de la variable a continuación, a la derecha del componente, otra alternativa sería comprobar como el valor de la variable efectivamente no ha cambiado, que es justo lo que se hace en la última línea.

Ejemplo 3

En esta ocasión, vamos a hacer lo mismo que en el ejemplo anterior, pero vamos a pinchar en confirmar cambios. Esto debería sobreescribir el valor de nuestra variable con el día que hemos puesto en el componente. Así que de forma análoga al ejemplo anterior en esta ocasión comprobamos que la fecha sí se ha cambiado.

    it('Date is changed when we confirm changes', () => {

        cy.get('input:first').clear().type('12 12 2023', {force: true})

        cy.get('[role=dialog] button').eq(1).click()

        cy.get('.TextWithLeftPadding').should('have.text', 'Frontend technologies will be as mature as backend technologies in Tue Dec 12 2023')
    })

AVISO IMPORTANTE: He querido hacer un test de fechas porque siempre tenemos que tener mucho cuidado con ellas. Es muy fácil meter la pata con este tipo de test. Solo tenemos que hardcodear el día de hoy, en mi caso «27 01 2022» en el campo de fecha para fastidiarla. Y es que este test nos funcionará hoy, seguramente el pipeline de ejecución funcionará, y mañana o el lunes cuando se metan nuevas historias y se pase de nuevo el pipeline de ejecución nuestros tests de cypress fallarán, porque hemos hardcodeado una fecha, y ha pasado un día, con lo que hoy ya no coinciden.

7. Conclusiones

Personalmente cada vez que tengo que montar un proyecto de cero, o añadir librerías nuevas tengo la sensación de que paso más tiempo en stackoverflow y en el foro de bugs de la librería que en la documentación de la misma.

Claro en este escenario subir versiones por temas de seguridad o rendimiento me da confianza cero, ya que cuando has conseguido que todas las librerías funcionen entre ellas el proyecto queda en el estado mírame y no me toques.

En cuanto a cypress me he llevado una sorpresa, ya que usándolo durante meses, los tests no son tan fáciles de romper como tenía entendido. No tienen esa fragilidad que me habían comentado. Además creo que aportan muchísimo valor a la hora de evitar bugs en producción. Personalmente me encanta ver como se ejecutan en el modo visual. En el modo no visual incluso se graba un vídeo de la ejecución de los mismos bajo la carpeta «videos». Así que desde aquí te recomiendo que lo utilices, así no solo estarán cubiertas tus funciones, sino la correcta ejecución de las mismas bajo las acciones de un usuario.

Consultor tecnológico de desarrollo de proyectos informáticos.
Ingeniero técnico en informática de gestión y graduado en ingeniería del software por la Universidad Politécnica de Madrid.
Puedes encontrarme en Autentia: Ofrecemos servicios de soporte a desarrollo, factoría y formación.
Somos expertos en Java/Java EE

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad