ProvenPin Install

axios typed API client with axios-mock-adapter: working versions (tested 2026-10-17)

Build a typed axios client with a baseURL and an auth header interceptor, then mock GET, POST, 404, network error and timeout offline while asserting parsed data, the header seen by the mock and error handling.

passed5 passed · 0 failed2026-10-17 05:58 UTC
Status
passed
Last passed
2026-10-17 05:58 UTC
Tests
5 / 5
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
axios@1.20.0axios-mock-adapter@2.1.0@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 39008163431

The code

Exactly the files that passed. Your agent can pull them with get_recipe("axios-mock-adapter-client") (install), or see them on GitHub.

package.json
{
  "name": "axios-mock-adapter-client",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "axios": "1.20.0"
  },
  "devDependencies": {
    "axios-mock-adapter": "2.1.0",
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
src/client.ts
import axios, {
  AxiosError,
  type AxiosInstance,
  type InternalAxiosRequestConfig,
} from "axios";

export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

export interface CreateTodoInput {
  title: string;
  completed?: boolean;
}

export interface ApiClientOptions {
  baseURL: string;
  token: string;
  timeoutMs?: number;
}

export class ApiError extends Error {
  readonly status?: number;
  readonly code?: string;

  constructor(message: string, options: { status?: number; code?: string } = {}) {
    super(message);
    this.name = "ApiError";
    this.status = options.status;
    this.code = options.code;
  }
}

export interface ApiClient {
  http: AxiosInstance;
  listTodos(): Promise<Todo[]>;
  getTodo(id: number): Promise<Todo>;
  createTodo(input: CreateTodoInput): Promise<Todo>;
}

export function createApiClient(options: ApiClientOptions): ApiClient {
  const { baseURL, token, timeoutMs = 1000 } = options;

  const http = axios.create({
    baseURL,
    timeout: timeoutMs,
    headers: { "Content-Type": "application/json" },
  });

  http.interceptors.request.use((config: InternalAxiosRequestConfig) => {
    config.headers.set("Authorization", `Bearer ${token}`);
    return config;
  });

  http.interceptors.response.use(
    (response) => response,
    (error: AxiosError) => {
      const isTimeout =
        error.code === "ECONNABORTED" || /timeout/i.test(error.message);
      if (isTimeout) {
        return Promise.reject(
          new ApiError("Request timed out", { code: error.code ?? "ECONNABORTED" }),
        );
      }
      if (!error.response) {
        return Promise.reject(new ApiError("Network error", { code: error.code }));
      }
      return Promise.reject(
        new ApiError(`Request failed with status ${error.response.status}`, {
          status: error.response.status,
          code: error.code,
        }),
      );
    },
  );

  return {
    http,
    async listTodos() {
      const response = await http.get<Todo[]>("/todos");
      return response.data;
    },
    async getTodo(id) {
      const response = await http.get<Todo>(`/todos/${id}`);
      return response.data;
    },
    async createTodo(input) {
      const response = await http.post<Todo>("/todos", input);
      return response.data;
    },
  };
}
test/client.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import MockAdapter from "axios-mock-adapter";
import { ApiError, createApiClient, type Todo } from "../src/client.ts";

const BASE_URL = "https://api.example.test";
const TOKEN = "test-token";

function makeClient(timeoutMs = 1000) {
  const api = createApiClient({ baseURL: BASE_URL, token: TOKEN, timeoutMs });
  const mock = new MockAdapter(api.http, { onNoMatch: "throwException" });
  return { api, mock };
}

function readAuthHeader(config: { headers?: unknown }): string | undefined {
  const headers = config.headers;
  if (!headers) return undefined;
  const getter = (headers as { get?: (name: string) => unknown }).get;
  if (typeof getter === "function") {
    const value = getter.call(headers, "Authorization");
    return value == null ? undefined : String(value);
  }
  const value = (headers as Record<string, unknown>)["Authorization"];
  return value == null ? undefined : String(value);
}

test("GET parses data and the request carries the auth header", async (t) => {
  const { api, mock } = makeClient();
  t.after(() => mock.restore());

  const todos: Todo[] = [
    { id: 1, title: "write a recipe", completed: false },
    { id: 2, title: "ship it", completed: true },
  ];
  let seenAuth: string | undefined;
  mock.onGet("/todos").reply((config) => {
    seenAuth = readAuthHeader(config);
    return [200, todos];
  });

  const result = await api.listTodos();

  assert.deepEqual(result, todos);
  assert.equal(seenAuth, `Bearer ${TOKEN}`);
});

test("POST sends the body and parses the created resource", async (t) => {
  const { api, mock } = makeClient();
  t.after(() => mock.restore());

  let seenBody: unknown;
  mock.onPost("/todos").reply((config) => {
    seenBody = JSON.parse(String(config.data));
    return [201, { id: 7, title: "ship it", completed: false }];
  });

  const created = await api.createTodo({ title: "ship it" });

  assert.deepEqual(seenBody, { title: "ship it" });
  assert.deepEqual(created, { id: 7, title: "ship it", completed: false });
});

test("404 rejects with an ApiError that keeps the status", async (t) => {
  const { api, mock } = makeClient();
  t.after(() => mock.restore());

  mock.onGet("/todos/99").reply(404, { message: "not found" });

  await assert.rejects(
    () => api.getTodo(99),
    (error: unknown) => {
      assert.ok(error instanceof ApiError);
      assert.equal(error.status, 404);
      assert.equal(error.message, "Request failed with status 404");
      return true;
    },
  );
});

test("network error rejects with an ApiError", async (t) => {
  const { api, mock } = makeClient();
  t.after(() => mock.restore());

  mock.onGet("/todos/1").networkError();

  await assert.rejects(
    () => api.getTodo(1),
    (error: unknown) => {
      assert.ok(error instanceof ApiError);
      assert.equal(error.message, "Network error");
      return true;
    },
  );
});

test("timeout rejects with a timeout ApiError", async (t) => {
  const { api, mock } = makeClient(25);
  t.after(() => mock.restore());

  mock.onGet("/todos").timeout();

  await assert.rejects(
    () => api.listTodos(),
    (error: unknown) => {
      assert.ok(error instanceof ApiError);
      assert.equal(error.message, "Request timed out");
      assert.equal(error.code, "ECONNABORTED");
      return true;
    },
  );
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.