Cómo hacer tests unitarios en Redux

Cómo hacer tests unitarios en Redux

Daniel Almeria AppsWeb

¿Qué es Jest? ¿Qué es Redux? ¿Qué tienen que ver con React? En este post te descubrimos los test unitarios y cómo encontrar errores rápidamente a través de estos. Los tests automatizados deben formar parte de cualquier desarrollo, ya que aportan una garantía adicional a la hora de incluir nuevas funcionalidades en el software.

 

Las pruebas proveen a los desarrolladores una garantía adicional a la hora de incluir nuevas funcionalidades en el software. Su utilización verifica de forma instantánea y segura que los cambios que están a punto de realizarse no afectan al funcionamiento de otras partes del programa.

Los tests unitarios son la forma de comprobar el correcto funcionamiento de una unidad de código (funciones, componentes, etc). Se utilizan para confirmar que una parte específica de código funciona de manera individual. Que hace “lo que se espera que haga”. 

Los tests unitarios deben seguir una serie de normas conocidas como FIRST :

  • Rápidos (Fast). Los tests deben ejecutarse muy rápido.
  • Independientes (Independent). Un test debe poder ejecutarse de forma independiente al resto.
  • Repetibles (Repeatable). El resultado debe ser el mismo independientemente de donde se ejecuten
  • Autoevaluables (Self-Validating). Cada test comprueba una unidad de código cada vez.
  • Completos (Thorough).  Deben cubrir todos los casos de uso sin realizar afirmaciones innecesarias.

Entre las numerosas ventajas de utilizar tests unitarios podemos citar que detectan errores de forma temprana, facilitan las refactorizaciones y sirven de documentación. También hay que tener en mente que son un indicador más de calidad.

 Los tests automatizados deberían formar parte de cualquier desarrollo, ya que además de aportar múltiples beneficios, nos permiten dormir más tranquilos 😉.

Daniel Almería, desarrollador senior del Departamento de Mobile en Cloud District

Jest

Jest es una plataforma de testing desarrollada por el equipo de Facebook. Jest es utilizado por Facebook para testear todo su código JavaScript incluyendo las aplicaciones React. Una de sus filosofías es proporcionar una experiencia integrada de “configuración cero”. La integración es muy sencilla ya que viene preconfigurado en el boilerplate create-react-app. Su ejecución es muy rápida (se puede configurar para que sólo se ejecuten los test asociados al código que ha cambiado) y tiene la posibilidad de añadir pruebas a la UI con React Testing Library.

Redux

¿Qué es React? React es una librería que se encarga exclusivamente de mostrar las vistas en pantalla, dejando la lógica de negocio a un lado. Ahí es donde entra Redux

Redux es patrón de desarrollo que ayuda a escribir aplicaciones que se comportan de manera consistente, y por lo tanto son más fáciles de probar y mantener. Permite manejar el estado de la aplicación de forma predecible siguiendo el patrón de desarrollo Flux. Aunque no es una librería exclusiva de React, se integra a la perfección y ambas funcionan como un todo. 

Esta librería provee una gran experiencia de desarrollo, gracias sobre todo a la edición en vivo combinada con un depurador sobre una línea de tiempo, las Redux DevTools

Estructura de Redux

Redux no te obliga a tener ningún tipo de estructura de ficheros, pero evidentemente para poder gestionar proyectos más o menos extensos la vas a necesitar. Para manejar los flujos asíncronos hay varias opciones, en este ejemplo utilizamos el middleware redux-thunk .

En esta estructura de carpeta, tenemos 5 archivos separados por responsabilidad:

  • Types: donde declaramos los tipos como constantes.
  • Reducer: donde tenemos el reducer asociado y su estado inicial.
  • Actions: donde declaramos las acciones simples (actions creators)
  • Operations: donde agrupamos las acciones asíncronas (thunks).
  • Tests: donde colocamos los tests.
redux/
    ├── auth/
        ├── index.js
        ├── auth.actions.js      
        ├── auth.operations.js   
        ├── auth.reducers.js     
        ├── auth.tests.js        
        ├── auth.types.js

 

// auth.types.js

export const SET_IS_FETCHING = 'auth/SET_IS_FETCHING';
export const SET_TOKEN = 'auth/SET_TOKEN';

 

// auth.actions.js

import * as types from './auth.types';

export const setIsFetching = isFetching => ({
    type: types.SET_IS_FETCHING,
    payload: isFetching,
});

export const setToken = token => ({
    type: types.SET_TOKEN,
    payload: token,
});

 

// auth.reducer.js

import * as types from './auth.types';

export const initialState = {
    isFetching: false,
    token: null,
};

export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case types.SET_IS_FETCHING:
      return { ...state, isFetching: action.payload };
    case types.SET_TOKEN:
      return { ...state, token: action.payload };
    default:
      return state;
  }
}

 

// auth.operations.js

import * as actions from './auth.actions';

export const login = ({ email, password }) => (dispatch, getState, { api }) => {
  dispatch(actions.setIsFetching(true));
  return api
    .login({ email, password })
    .then(({ token }) => dispatch(actions.setToken(token)))
    .finally(() => dispatch(actions.setIsFetching(false)));
}


Testar los actions

Para testar las acciones (y su reducer asociado), comprobamos el estado inicial del store, ejecutamos la acción, y por último comprobamos que el estado se ha actualizado correctamente.

// auth.test.js

import { createStore } from 'redux';
import * as actions from './auth.actions';
import reducer from ‘./auth.reducer’;

test('setIsFetching', () => {
   const store = createStore(reducer);
   expect(store.getState().isFetching).toBeFalsy();

   store.dispatch(actions.setIsFetching(true));
   expect(store.getState().isFetching).toBeTruthy();
 });

test('setToken', () => {
   const store = createStore(reducer);
   expect(store.getState().token).toBeNull();

   const token = ‘XXX’;
   store.dispatch(actions.setToken(token));
   expect(store.getState().token).toEqual(token);
 });


Testar los thunks

Una vez tenemos testadas las acciones simples, es el turno de las acciones asíncronas (thunk). Es importante tener en cuenta que los thunks deben devolver una promesa (ya veremos por qué). 

// auth.test.js

import { createStore } from 'redux';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import reducer, { initialState } from './auth.reducer';
import * as operations from './auth.operations';
import * as types from './auth.types';
import * as api from '../../api/mock';

test('login', () => {
   const middlewares = [thunk.withExtraArgument({ api })];
   const mockStore = configureMockStore(middlewares);
   const store = mockStore({ auth: { ...initialState } });

   return store.dispatch(operations.login()).then(() => {
     const actions = store.getActions();
     expect(actions[0].type).toEqual(types.SET_IS_FETCHING);
     expect(actions[1].type).toEqual(types.SET_TOKEN);
     expect(actions[2].type).toEqual(types.SET_IS_FETCHING);
   });
 });

 

Con este tipo de test vamos a comprobar que las acciones se ejecutan el orden correcto (y no si el estado cambia, ya que esto se prueba en los otros test). Para ello utilizamos la librería redux-mock-store.

Comenzamos creando los datos necesarios para el test. Como el login realiza una llamada al api, tenemos que mockearla e inyectarla. En este paso podemos inyectar cualquier librería que tengamos añadida como argumento extra a redux-thunk. A continuación creamos el mockStore y lanzamos el login, que como recordaremos es una promesa. Al completarse comprobamos que las acciones se han ejecutado en el orden correcto. 

 

En este caso y por simplificar sólo hemos comprobado el camino feliz, pero también hay que controlar los posibles casos de error.

Daniel Almería, desarrollador senior del Departamento de Mobile en Cloud District

Conclusiones

Hacer pruebas unitarias de nuestro código es importante para evitarnos problemas y encontrar errores más rápido.

En el caso de Redux, la mayor parte del código que escribas van a ser funciones puras, esto hace que crear pruebas unitarias para nuestra aplicación sea más fácil que nunca

No hay excusa para no hacer pruebas y ser mejores desarrolladores.

Contáctanos

INFORMACIÓN IMPORTANTE SOBRE COOKIES

Este sitio web utiliza cookies propias y de analítica para mejorar tu experiencia. Algunas de estas cookies son imprescindibles para que el site funcione correctamente.
Puedes ver más información sobre las cookies en nuestra Política de privacidad.

Aceptar