Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Create API Test

When we create a new API, it is advisable to create test cases for a newly created API in order to ensure that all potential scenarios of the API are covered.

  1. Create a mock server, we will use https://mswjs.io/

    • In /mocks/api/handlers, specify the request (method, path, and a function that would return the mocked response)
    import { rest } from "msw";
    import { mockGetCity } from "./city";
    
    const API_BASE_URL = "https://hhstaging.hungryhub.com/api";
    export const handlers = [
      rest.get(`${API_BASE_URL}/v5/cities.json`, mockGetCity),
    ];
    
    • Create a new file inside /mocks/api to provide response resolver, for example city.ts. Response resolver is a function that accepts

      • req, an information about a matching request;
      • res, a functional utility to create the mocked response;
      • ctx, a group of functions that help to set a status code, headers, body, etc. of the mocked response
    • Create simulations and mocked responses when the API returns

      • a successfull response
      • a 500 error
      • a null response
      • an empty string response
      • an empty array response
      import { ResponseFunction, MockedRequest, RestContext } from "msw";
      
      export const mockGetCity = (
        req: MockedRequest,
        res: ResponseFunction,
        ctx: RestContext
      ) => {
        // simulate if no response
        if (req.url.searchParams.get("simulate") === "return-nothing") {
          return res();
        }
        // simulate if backend error 500
        if (req.url.searchParams.get("simulate") === "error-500") {
          return res(
            ctx.status(500),
            ctx.json({
              message: "Someting went wrong",
            })
          );
        }
      
        if (req.url.searchParams.get("simulate") === "return-null") {
          return res(ctx.json(null));
        }
      
        if (req.url.searchParams.get("simulate") === "return-string") {
          return res(ctx.json(""));
        }
      
        if (req.url.searchParams.get("simulate") === "return-array") {
          return res(ctx.json([]));
        }
      
        // simulate if return correct object
        return res(
          ctx.json({
            data: [
              {
                id: "1",
                type: "cities",
                attributes: {
                  name: "Bangkok",
                  home_description: "Hungry Hub Summer Hot Deals 2023",
                  icon: {
                    url: "/uploads/city/icon/1/Bangkok.png",
                  },
                  country_id: 218,
                  is_mini_homepage: false,
                  country_icon: {
                    url: "/uploads/country/icon/218/Flag_Icon.png",
                  },
                  total_restaurants: 1549,
                },
              },
            ],
            success: true,
            message: "",
          })
        );
      };
      
    • Configure server

      • Create mocks/api/server.ts file
      • Create a server instance with our request handlers defined earlier
        // src/mocks/server.js
        import { setupServer } from "msw/node";
        import { handlers } from "./handlers";
        // This configures a request mocking server with the given request handlers.
        export const server = setupServer(...handlers);
        
  2. Create test cases

    • Create .spec.ts file, for example city.spec.ts
    • Import functions from vitest
    • Import function that will be tested
    • Import mock server that we created earlier
    import { expect, test, vi, beforeAll, afterEach, afterAll } from "vitest";
    import { getCities } from "./city";
    import { server } from "~/mocks/api/server";
    
    • Use lifecycle to start, reset, and close the server
    beforeAll(() => server.listen());
    afterEach(() => server.resetHandlers());
    afterAll(() => server.close());
    
    • Mock states if needed, in this case, we need useLang and useRuntimeConfig
    vi.mock("~/composables/state/lang", () => ({
      useLang: () => {
        return {
          value: "en",
        };
      },
    }));
    
    vi.mock("#imports", () => ({
      useRuntimeConfig: () => {
        return {
          public: {
            apiDomain: "https://hhstaging.hungryhub.com",
          },
        };
      },
    }));
    
    • Create test case scenarios when the API returns

      • a successfull response
      • a 500 error
      • a null response
      • an empty string response
      • an empty array response
      • different response from normal response
      test("normal fetch", async () => {
        const result = await getCities();
        console.log("res", result.message, result.data);
        expect(result.isSuccess).toBe(true);
      });
      
      test("fetch when backend error 500", async () => {
        const result = await getCities({
          simulate: "error-500",
        });
        console.log("res", result.message, result.data);
        expect(result.isSuccess).toBe(true);
      });
      
      test("fetch when backend return null", async () => {
        const result = await getCities({
          simulate: "return-null",
        });
        console.log("res", result.message, result.data);
        expect(result.isSuccess).toBe(true);
      });
      
      test("fetch when backend not return anything", async () => {
        const result = await getCities({
          simulate: "return-nothing",
        });
        console.log("res", result.message, result.data);
        expect(result.isSuccess).toBe(true);
      });
      
      test("fetch when backend return invalid type (string)", async () => {
        const result = await getCities({
          simulate: "return-string",
        });
        console.log("res", result.message, result.data);
        expect(result.isSuccess).toBe(true);
      });