January 2, 2025
Jenkins vs GitHub Actions
I recently came across an interesting article by the team at Slack, where they share their experience migrating from Jenkins to GitHub Actions (GHA).
To ensure reliability in Vue.js applications, it is essential to implement effective testing. In this article, we will explore how to build a Pinia store that interacts with an external API and how to write tests to validate its behavior and error handling.
First, we define a Pinia store that handles data from an API. Here is an example where we store information retrieved from an external API:
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";
}
}
}
});
In this store, the fetchData function makes a request to an external API and stores the returned data in state.
For testing, we use Vitest and create a mock for useAsyncData, the function that performs the API request. Here is how to configure the 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 for useAsyncData
const useAsyncDataMock = vi.hoisted(() => vi.fn());
mockNuxtImport("useAsyncData", () => useAsyncDataMock);
const mockApiData = {
id: 1,
name: "Example",
detail: "This is an example"
};
// Mock for successful data
const setupSuccessMock = () => {
useAsyncDataMock.mockImplementation(() => {
return {
data: ref(mockApiData),
error: ref(null),
};
});
};
afterEach(() => {
cleanup();
useAsyncDataMock.mockClear();
});
describe("Data Store", () => {
beforeEach(() => {
setActivePinia(createPinia());
});
describe("when data is retrieved successfully", () => {
it("should set the data in the store", async () => {
const store = useDataStore();
setupSuccessMock();
await store.fetchData("https://api.example.com/data");
expect(store.data).toEqual(mockApiData);
});
});
describe("when data has already been fetched", () => {
it("should not make the request again", 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("when the request fails", () => {
it("should set an error message", 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");
});
});
});
Below, we break down how the key tests for this store work:
Successful data mock: We use setupSuccessMock to simulate a successful response from the external API, ensuring that the store saves the data correctly.
const setupSuccessMock = () => {
useAsyncDataMock.mockImplementation(() => {
return {
data: ref(mockApiData),
error: ref(null),
};
});
};
Data storage verification: We test that the retrieved data is stored in the store after a successful request. If the request has already been made previously, we verify that it is not made again.
describe("when data is retrieved successfully", () => {
it("should set the data in the store", async () => {
const store = useDataStore();
setupSuccessMock();
await store.fetchData("https://api.example.com/data");
expect(store.data).toEqual(mockApiData);
});
});
Error handling: A request failure is simulated to verify that the store updates its error state correctly.
describe("when the request fails", () => {
it("should set an error message", 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");
});
});
With this setup, you can ensure that your store handles API data and errors correctly, which is essential for maintaining the integrity and reliability of applications that depend on external data.