Back to Repositories

Testing API Retry Mechanism in github-readme-stats

This test suite validates the retry mechanism implementation for handling GitHub API rate limiting in the github-readme-stats repository. It ensures robust error handling and automatic retry functionality for API requests that encounter rate limiting issues.

Test Coverage Overview

The test suite provides comprehensive coverage of the retryer utility function, focusing on three critical scenarios:
  • Successful first-attempt API calls
  • Successful retry after initial failure
  • Maximum retry limit handling
The tests verify both happy path and error scenarios, ensuring proper handling of GitHub’s rate limiting responses.

Implementation Analysis

The testing approach utilizes Jest’s mocking capabilities to simulate API responses and rate limiting scenarios. Mock implementations are crafted to test different retry patterns using Promise-based async/await syntax. The tests leverage Jest’s function spying to verify exact call counts and response handling.

Technical Details

Testing stack includes:
  • Jest testing framework
  • @testing-library/jest-dom for DOM assertions
  • Mock functions (jest.fn()) for API simulation
  • Promise-based async testing
  • Custom logger integration

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolated test cases, proper async/await handling, and comprehensive error scenario coverage. Each test case focuses on a specific aspect of the retry mechanism, with clear assertions and error expectations. The mock implementations are well-structured to simulate real-world API behavior.

anuraghazra/github-readme-stats

tests/retryer.test.js

            
import { jest } from "@jest/globals";
import "@testing-library/jest-dom";
import { retryer, RETRIES } from "../src/common/retryer.js";
import { logger } from "../src/common/utils.js";
import { expect, it, describe } from "@jest/globals";

const fetcher = jest.fn((variables, token) => {
  logger.log(variables, token);
  return new Promise((res) => res({ data: "ok" }));
});

const fetcherFail = jest.fn(() => {
  return new Promise((res) =>
    res({ data: { errors: [{ type: "RATE_LIMITED" }] } }),
  );
});

const fetcherFailOnSecondTry = jest.fn((_vars, _token, retries) => {
  return new Promise((res) => {
    // faking rate limit
    if (retries < 1) {
      return res({ data: { errors: [{ type: "RATE_LIMITED" }] } });
    }
    return res({ data: "ok" });
  });
});

describe("Test Retryer", () => {
  it("retryer should return value and have zero retries on first try", async () => {
    let res = await retryer(fetcher, {});

    expect(fetcher).toBeCalledTimes(1);
    expect(res).toStrictEqual({ data: "ok" });
  });

  it("retryer should return value and have 2 retries", async () => {
    let res = await retryer(fetcherFailOnSecondTry, {});

    expect(fetcherFailOnSecondTry).toBeCalledTimes(2);
    expect(res).toStrictEqual({ data: "ok" });
  });

  it("retryer should throw specific error if maximum retries reached", async () => {
    try {
      await retryer(fetcherFail, {});
    } catch (err) {
      expect(fetcherFail).toBeCalledTimes(RETRIES + 1);
      expect(err.message).toBe("Downtime due to GitHub API rate limiting");
    }
  });
});