EmilianoAugust 16, 2024

Testear un store de Pinia en Nuxt 3 utilizando Vitest

Para garantizar la fiabilidad en aplicaciones Vue.js, es esencial implementar pruebas efectivas. En este artículo, exploraremos cómo construir un store en Pinia que interactúe con una API externa y cómo realizar tests para validar su comportamiento y manejo de errores.

Creando el store de Pinia

Primero, definimos un store de Pinia que maneje los datos de una API. Aquí tienes un ejemplo donde almacenamos información obtenida de una API externa:

import { defineStore } from "pinia";

export const useDataStore = defineStore({
  id: "data",
  state: () => ({
    data: null,
    error: {
      message: "",
      status: false
    }
  }),
  actions: {
    async fetchData(apiUrl: string) {
      try {
        if (this.data) {
          return;
        }
        const { data } = await useAsyncData("data", () =>
          $fetch(apiUrl)
        );
        if (data.value) {
          this.data = data.value;
        }
      } catch (error) {
        this.error.status = true;
        this.error.message = "Error fetching data";
      }
    }
  }
});

En este store, la función fetchData realiza una solicitud a una API externa y almacena los datos obtenidos en el estado.

Mockeando useAsyncData en los tests

Para las pruebas, utilizamos Vitest y creamos un mock de useAsyncData, la función que hace la solicitud a la API. Aquí te muestro cómo configurar los tests:

import { cleanup } from "@testing-library/vue";
import { vi, describe, it, expect, afterEach, beforeEach } from "vitest";
import { mockNuxtImport } from "@nuxt/test-utils/runtime";
import { setActivePinia, createPinia } from "pinia";
import { useDataStore } from "@/stores/data";

// Mock para useAsyncData
const useAsyncDataMock = vi.hoisted(() => vi.fn());
mockNuxtImport("useAsyncData", () => useAsyncDataMock);

const mockApiData = {
  id: 1,
  name: "Example",
  detail: "This is an example"
};

// Mock para datos exitosos
const setupSuccessMock = () => {
  useAsyncDataMock.mockImplementation(() => {
    return {
      data: ref(mockApiData),
      error: ref(null),
    };
  });
};

afterEach(() => {
  cleanup();
  useAsyncDataMock.mockClear();
});

describe("Data Store", () => {
  beforeEach(() => {
    setActivePinia(createPinia());
  });

  describe("cuando los datos se obtienen con éxito", () => {
    it("debería establecer los datos en el store", async () => {
      const store = useDataStore();
      setupSuccessMock();
      await store.fetchData("https://api.example.com/data");

      expect(store.data).toEqual(mockApiData);
    });
  });

  describe("cuando los datos ya han sido obtenidos", () => {
    it("no debería volver a hacer la solicitud", async () => {
      const store = useDataStore();
      setupSuccessMock();
      await store.fetchData("https://api.example.com/data");
      await store.fetchData("https://api.example.com/data");

      expect(useAsyncDataMock).toHaveBeenCalledTimes(1);
    });
  });

  describe("cuando la solicitud falla", () => {
    it("debería establecer un mensaje de error", async () => {
      const store = useDataStore();
      useAsyncDataMock.mockImplementation(() => {
        throw new Error("Error fetching data");
      });
      await store.fetchData("https://api.example.com/data");

      expect(store.error.status).toBe(true);
      expect(store.error.message).toBe("Error fetching data");
    });
  });
});

Configuración y análisis de los tests

A continuación, desglosaremos cómo se realizan los tests clave para este store:
Mock de datos exitosos: Utilizamos setupSuccessMock para simular una respuesta exitosa de la API externa, asegurándonos de que el store guarde los datos de forma correcta

const setupSuccessMock = () => {
  useAsyncDataMock.mockImplementation(() => {
    return {
      data: ref(mockApiData),
      error: ref(null),
    };
  });
};

Verificación de almacenamiento de datos: Probamos que los datos obtenidos se guarden en el store tras una solicitud exitosa. Si la solicitud ya se ha hecho previamente, verificamos que no se vuelva a realizar.

describe("cuando los datos se obtienen con éxito", () => {
  it("debería establecer los datos en el store", async () => {
    const store = useDataStore();
    setupSuccessMock();
    await store.fetchData("https://api.example.com/data");

    expect(store.data).toEqual(mockApiData);
  });
});

Manejo de errores: Se simula un fallo en la solicitud para verificar que el store actualice su estado de error adecuadamente.

describe("cuando la solicitud falla", () => {
  it("debería establecer un mensaje de error", async () => {
    const store = useDataStore();
    useAsyncDataMock.mockImplementation(() => {
      throw new Error("Error fetching data");
    });
    await store.fetchData("https://api.example.com/data");

    expect(store.error.status).toBe(true);
    expect(store.error.message).toBe("Error fetching data");
  });
});

Con esta configuración, puedes asegurarte de que tu store maneja los datos y errores de la API correctamente, lo cual es esencial para mantener la integridad y fiabilidad de las aplicaciones que dependen de datos externos.