[Unit testing] Testing a fetch promise

Code:

import type { paths } from "@octokit/openapi-types";

type OrgRepoResponse =
  paths["/repos/{owner}/{repo}"]["get"]["responses"]["200"]["content"]["application/json"];

export type Fetch = typeof fetch;

export class GithubApi {
  constructor(
    private token: string | undefined,
    private fetch: Fetch = fetch,
    private delay: (ms: number) => Promise<void> = delay
  ) {}

  async getRepositories(user: string) {
    let page = 1;
    const repos: OrgRepoResponse[] = [];
    while (true) {
      const response = await this.fetch(
        `https://api.github.com/users/${user}/repos?per_page=30&page=${page}`,
        {
          headers: {
            "User-Agent": "Qwik Workshop",
            "X-GitHub-Api-Version": "2022-11-28",
          },
        }
      );
      const json = (await response.json()) as OrgRepoResponse[];
      repos.push(...json);
      if (json.length < 30) {
        break;
      }
      page++;
    }
    return repos;
  }

  async getRepository(user: string, repo: string) {
    const headers: HeadersInit = {
      "User-Agent": "Qwik Workshop",
      "X-GitHub-Api-Version": "2022-11-28",
    };
    if (this.token) {
      headers["Authorization"] = "Bearer " + this.token;
    }

    return Promise.race([
      this.delay(4000).then(() => {
        return { response: "timeout" };
      }),
      this.fetch(`https://api.github.com/repos/${user}/${repo}`, {
        headers,
      }).then((response) => {
        return response.json();
      }),
    ]);
  }
}

export function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

 

Test:

import { describe, test, vi, beforeEach, Mock } from "vitest";
import { Fetch, GithubApi, delay } from "./api";

describe("github-api", () => {
  let fetchMock: Mock<Parameters<Fetch>, Promise<Response>>;
  let api: GithubApi;
  let delayMock: Mock<[number], Promise<void>>;

  beforeEach(() => {
    delayMock = vi.fn<[number], Promise<void>>(mockPromise);
    fetchMock = vi.fn<Parameters<Fetch>, Promise<Response>>(mockPromise);
    api = new GithubApi("TOKEN", fetchMock, delayMock);
  });

  describe("getRepository", () => {
    test("should return repository information", async ({ expect }) => {
      const responsePromise = api.getRepository("USERNAME", "REPO");

      expect(fetchMock).toHaveBeenCalledWith(
        "https://api.github.com/repos/USERNAME/REPO",
        {
          headers: {
            "User-Agent": "Qwik Workshop",
            "X-GitHub-Api-Version": "2022-11-28",
            Authorization: "Bearer TOKEN",
          },
        }
      );
      fetchMock.mock.results[0].value.resolve(new Response('"MockResponse"'));
      expect(await responsePromise).toEqual("MockResponse");
    });

    test("should timeout after x seconds with time out response", async ({
      expect,
    }) => {
      const responsePromise = api.getRepository("USERNAME", "REPO");

      expect(fetchMock).toHaveBeenCalledWith(
        "https://api.github.com/repos/USERNAME/REPO",
        {
          headers: {
            "User-Agent": "Qwik Workshop",
            "X-GitHub-Api-Version": "2022-11-28",
            Authorization: "Bearer TOKEN",
          },
        }
      );
      expect(delayMock).toHaveBeenCalledWith(4000);
      delayMock.mock.results[0].value.resolve();
      expect(await responsePromise).toEqual({ response: "timeout" });
    });
  });

  describe("getRepositories", () => {
    test("should fetch all repositories for a user", async ({ expect }) => {
      const responsePromise = api.getRepositories("USERNAME");
      expect(fetchMock).toHaveBeenCalledWith(
        "https://api.github.com/users/USERNAME/repos?per_page=30&page=1",
        expect.any(Object)
      );
      const repoSet1 = new Array(30).fill(null).map((_, i) => ({ id: i }));
      fetchMock.mock.results[0].value.resolve(
        new Response(JSON.stringify(repoSet1))
      );
      await delay(0);
      const repoSet2 = [{ id: 30 }];
      fetchMock.mock.results[1].value.resolve(
        new Response(JSON.stringify(repoSet2))
      );
      expect(await responsePromise).toEqual([...repoSet1, ...repoSet2]);
    });
  });
});

function mockPromise<T>(): Promise<T> & {
  resolve: typeof resolve;
  reject: typeof reject;
} {
  let resolve!: (value: T) => void;
  let reject!: (error: any) => void;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  }) as any;
  promise.resolve = resolve;
  promise.reject = reject;
  return promise;
}

 

posted @ 2024-04-19 14:25  Zhentiw  阅读(5)  评论(0编辑  收藏  举报