Testing de componentes de React

El proceso de testing es vital para reducir la cantidad de posibles errores en nuestras aplicaciones. En este artículo vamos a usar Enzyme y Jest para testear nuestras funciones y componentes en React.

Lectura de 9 minutos
21 ago. 2019
Diseño web
Testing de componentes de React

El proceso de testing es un proceso vital para reducir la cantidad de posibles errores en nuestras aplicaciones, lo cual no significa que estos errores vayan a desaparecer sino que vamos a reducir su tasa de aparición.

En este articulo vamos a introducir sobre como testear nuestras funciones y componentes en React, para este fin utilizaremos:

¿Que son los tests unitarios?

El testing unitario comprueba el funcionamiento de una pieza o unidad de código de forma aislada del resto de la aplicación, para comprobar si el código que escribimos hace lo que tiene que hacer y de la manera adecuada. A la vez podemos probar que nuestro código responda a las diferentes situaciones que se le puedan presentar al usuario.

Consideremos el siguiente código:

const fizzbuzz = (n) => {
    if (n % 3 === 0 && n % 5 === 0) {
        return "fizzbuzz";
    }

    if (n % 3 === 0) {
        return "fizz";
    }

    if (n % 5 === 0) {
        return "buzz";
    }

    return null;
}

Esta función es la función fizzbuzz está retorna "fizzbuzz" en caso que el numero pasado como parámetro sea divisible tanto por 3 como por 5, devuelve "fizz" si solo es divisible entre 3, devuelve "buzz" si es divisible solo entre 5 y en caso no cumplir con estas condiciones devuelve un null.

Ahora, como verificar que este código cumple con estas condiciones? ahi es donde entran nuestros tests.

describe('Tests para la funcion fizzbuz', () => {
  it('Deberia retornar fizzbuzz dado un numero divisible entre 3 y 5', () => {
    expect(fizzbuzz(15)).toBe("fizzbuzz")
  })
})

En este código hacemos lo siguiente:

  1. Verificanmos la primera condición: que la función retorne "fizzbuzz" si recibe como parámetro un numero divisible entre 3 y 5.
  2. La función describe() agrupa tests de una unidad de código y como parámetros tendremos la descripción de lo que iremos a testear y un callback donde tendremos todos nuestros tests.
  3. La función it() es donde realizaremos el test y al igual que la función describe() recibe una descripción del test y una función para configurar el test.
  4. Realizamos nuestros assertions para verificar que nuestro código funcione de manera correcta, en este caso usamos la función expect() que recibe un valor y nos devuelve un objeto de tipo expectation que nos da la posibilidad de usar matchers los cuales son una serie de funciones que nos permite verificar el valor obtenido en el expect() de diferentes formas.
  5. En este caso estamos usando el matcher``toBe()` el cual compara de manera estricta el valor obtenido, es decir busca que el valor conseguido en el expect() sea igual al especificado, en este caso "fizzbuzz.

Si ejecutamos este test tendremos lo siguiente:

fizzbuzz - test

Como podemos ver nuestra función fizzbuzz() responde a este test de manera satisfactoria, pero que pasaría si nuestra función en vez de devolvernos "fizzbuzz" en esta situación nos devolvería "fizzbus"?, nuestro test debería fallar ya que estamos recibiendo un valor diferente al esperado.

fizzbuzz - test 2

Como podemos ver el valor recibido("fizzbus") es diferente al valor esperado en nuestro test("fizzbuzz"), y esta es la razón por la que nuestro test falla.

Esto tiene una serie de ventajas

  • Podemos refactorizar el código de nuestra función sabiendo que si alguno de nuestro cambios rompe su funcionamiento, lo sabremos de inmediato.
  • A la larga sirven como documentación de nuestro código, ya que los tests indican que hace cada unidad de código, incluyendo que valores recibe y que debería de retornar.

Además cabe destacar que tanto la función describe() , it() y expect() son inyectadas por Jest en el momento de ejecutar nuestros tests, por lo que no es necesaria definirlas o importarlas.

Para finalizar los tests de nuestra función fizzbuzz() vamos a verificar las otras condiciones presentes dentro de la función.

describe('Tests para la funcion fizzbuz', () => {
	// Tests creado anteriormente
  it('Deberia retornar fizzbuzz dado un numero divisible entre 3 y 5', () => {
    expect(fizzbuzz(15)).toBe("fizzbuzz")
  })

  it('Deberia retornar fizz dado un numero divisible entre 3', () => {
    expect(fizzbuzz(9)).toBe("fizz")
  })

  it('Deberia retornar buzz dado un numero divisible entre 5', () => {
    expect(fizzbuzz(25)).toBe("buzz")
  })
	
	it('Deberia retornar null si el numero no es divisible ni por 3 ni por 5', () => {
    expect(fizzbuzz(8)).toBe(null)
  })
})

Con estos tests ya estamos testeando los posibles escenarios de nuestra función fizzbuzz() y corriendo los tests de nuevo tendremos lo siguiente.

fizzbuzz - test 3

Si quieres saber mas sobre Jest, te invito a visitar su documentación y si quieres ver el código de estos tests, te dejo el siguiente enlace.

Testing de componentes de React

La idea de testear nuestro componentes es primero probar que estos componentes se rendericen sin errores y además que la lógica de negocio que estos componentes deben realizar se ejecute de manera exitosa, con esto en mente vamos a estar usando Enzyme en conjunto con Jest.

Consideremos el siguiente componente:

import React, { useState } from 'react';

function App() {
  const [count, setCount] = useState(0)

  function add() {
    setCount(count + 1)
  }

  function substract() {
    setCount(count - 1)
  }

  return (
    <section>
      <h1 id='counter-title'>Counter: {count}</h1>
      <button id='counter-add-button' onClick={add} className='counter__button'>Add</button>
      <button id='counter-substract-button' onClick={substract} className='counter__button'>Substract</button>
    </section>
  )
}

export default App;

Este componente es el típico componente del counter, a medida que se haga click sobre los botones de add o substract el contador se debería ir actualizando dependiendo de sobre cual de los dos botones se hizo click.

Primero que todo queremos saber si el componente se esta renderizando adecuadamente, para eso escribiremos el siguiente test

import App from '../src/App'
import { shallow } from 'enzyme'

describe('Tests para el counter de la aplicación', () => {
	it('Deberia renderizarse sin Error', () => {
		const wrapper = shallow(<App />)
		expect(wrapper).toHaveLength(1);
	})
})

En este test estamos haciendo uso de la función shallow() de Enzyme la cual nos permite testear solo el componente que deseamos sin tener en cuenta posibles componentes hijos que tenga este componente, luego estamos usando la función expect() de Jest, y usando el matcher``toHaveLength() verificamos la cantidad de nodos que tiene el componente, en este caso estamos verificando que exista el nodo raíz del componente, de ser así podemos asegurar que nuestro componente se renderizo sin errores.

Ahora, no solo queremos saber si nuestro componente se renderizó, también queremos saber si nuestros botones efectivamente agregan o disminuyen, para eso podemos tener el siguiente test

import App from '../src/App'
import { shallow } from 'enzyme'

describe('Tests para el counter de la aplicación', () => {
	it('Deberia renderizarse sin Error', () => {
		const wrapper = shallow(<App />)
		expect(wrapper).toHaveLength(1);
	})

	it('Deberia aumentar el contador cuando se haga click sobre el boton de add', () => {
		const wrapper = shallow(<App />)

		expect(wrapper.find('#counter-title').text().includes('0')).toBe(true)
		wrapper.find('#counter-add-button').simulate('click')

		expect(wrapper.find('#counter-title').text().includes('1')).toBe(true)
		wrapper.find('#counter-add-button').simulate('click')

		expect(wrapper.find('#counter-title').text().includes('2')).toBe(true)
	})
}) 

Primero que todo en nuestro test estamos usando de nuevo la función shallow() para renderizar nuestro componente, despues usando la función find() la cual nos permite buscar algun elemento html dentro del componente usando cualquier selector valido de css, envolviendo este elemento html en un elemento especial de Enzyme.

Con find() conseguimos nuestro titulo y verificamos que empiece con el valor de 0, para eso usamos la función text() que nos permite conseguir el texto interno que tiene nuestro titulo, ya con esto usamos la función includes() de los strings para saber si el texto pasado por parámetro existe en el string y ya sabiendo esto solo verificamos con el matcher``toBe() si esto nos devuelve true.

Además necesitamos saber si efectivamente el botón actualiza el estado del componente, para este fin conseguimos el botón usando la función find() y gracias a la envoltura que nos hace Enzyme sobre este elemento, podemos usar la función simulate() que simula la ejecución de un evento sobre nuestro botón, en este caso estamos simulando el evento click para verificar que después del primer click, debería de ser 1.

Para construir los tests de nuestro botón substract sería básicamente lo mismo:

import App from '../src/App'
import { shallow } from 'enzyme'

describe('Tests para el counter de la aplicación', () => {
	// Tests anteriores...

	it('Deberia disminuir el contador apretando el boton de substract', () => {
		const wrapper = shallow(<App />)

		expect(wrapper.find('#counter-title').text().includes('0')).toBe(true)
		wrapper.find('#counter-substract-button').simulate('click')

		expect(wrapper.find('#counter-title').text().includes('-1')).toBe(true)
		wrapper.find('#counter-substract-button').simulate('click')

		expect(wrapper.find('#counter-title').text().includes('-2')).toBe(true)
	})
})

Con este test estamos realizando básicamente la misma tarea que hicimos con nuestro botón de agregar pero con nuestro botón de restar, y verificamos si el titulo si refleja el cambio de nuestro estado.

Si quieres aprender más sobre como testear componentes de React y a configurar Enzyme en tus proyectos te invito a visitar la documentación de Enzyme y el Workshop sobre TDD que tenemos en EDteam.

También puedes aprender más sobre React con nuestra especialidad de cuatro cursos que te llevaran desde cero hasta dominar todos los conceptos de React.js. Al finalizar habrás creado tu propia plataforma de educación online que podrás vender a tus clientes y adaptarlo a sus necesidades. 👉ed.team/react

Especialidad React

Hasta pronto.

Recuerda iniciar sesión para comentar este articulo