Should I test that?

Should I test an API client?

Verdict

Yes

Yes, test an API client that you wrote for another company's API: serve recorded responses from a fake HTTP server, including error statuses and a second page of results, and assert the request the client sends and the values it returns.

Why

Yes, test an API client you wrote, at its HTTP boundary. The typical case is a backend class that wraps a third-party REST API, such as a CRM: it builds requests, maps JSON into your types, and turns error statuses into errors. Blast radius is users, because features built on the client show wrong data, and Change frequency is regularly, because the client gains a call or a field about once a month. Detectability is eventually, because a misread field or a skipped page looks plausible, and Reversibility is with-effort, because records saved from that data need a repair script. Test cost is moderate, because a fake server with recorded responses takes me about an hour per call, so rule R11 gives Test.

When the decision changes
WhenDecisionWhy
The API client creates charges through a payment providerTest mandatory: a recorded response for each outcome, including a decline and a timeout, and the same idempotency key on every retried chargeBlast radius rises to money and Reversibility to costly, because a double charge ends in a refund
The API client sends the signed-in user's account ID to a document storage API and shows the files it returnsTest mandatory: a test with two accounts that asserts the account ID in each request the fake server receivesBlast radius rises to safety-or-legal and Detectability to never, because another account's files look like normal output
The failure to catch is a change in the provider's responses, and the provider has no sandboxTest it differently: alert on production responses that fail to parseTest cost rises to heavy, because only production traffic shows the change
The provider pins your account to one API version, and the API client changes a few times a yearTest minimally: one test for each call, with its main recorded responseChange frequency falls to rarely, because the fields change only with a new API version
CI generates the API client from the provider's OpenAPI documentDo not test the generated client; the compiler rejects your code that reads a removed field, so test only the code that calls the clientDetectability moves to immediately and Reversibility to trivial, because the build fails before anything ships

What breaks if you don't test

A client checked by hand against one successful response meets its first real error in production. Such a client treats a 429 rate limit as an empty result, reads only the first page of a list, or maps created_at into the update date. Nothing crashes, so customers see plausible data and find the gap weeks later, after the app has saved the wrong values.

What you lose if you over-test

A unit test that mocks the HTTP library call by call only repeats the URL you typed in the client. That test breaks when you swap the library and passes when the real response has another shape, because you wrote both sides. Calling the real API from CI fails builds on the provider's rate limits and outages, and a live key can create real records.

How to test

  1. Record real responses from the provider's sandbox with WireMock: the main response, each error status the client handles, and a paged list.
  2. Serve the recordings from a fake server such as Mock Service Worker, set to fail on any request it has no handler for.
  3. Assert the request the client sends, then the values it returns or the error it raises.
  4. Cover only the calls your product makes.
  5. Once a day, outside the build, send the recorded requests to the sandbox as a contract test.

When the answer changes

  • The client moves money or fetches records that only one customer may see.
  • The provider changes its responses without notice and offers no sandbox.
  • A generator writes the client from the provider's OpenAPI document.

Real incident + Code example

The sync that stopped at 100 contacts

On a sales product I worked on, a nightly job imported each customer's contacts through our own CRM client. The client ignored next_cursor, and my hand-written fixtures held fewer than 100 contacts, the page size. For five weeks the larger accounts got only their first 100 contacts, until a customer asked where a colleague's leads were. A full import fixed the data in a day, and I added this test with two recorded pages:

import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { afterAll, beforeAll, expect, test } from "vitest";
import { CrmClient } from "./crmClient";
import page1 from "./fixtures/contacts-page-1.json"; // recorded, next_cursor "c2"
import page2 from "./fixtures/contacts-page-2.json"; // recorded, next_cursor null

const server = setupServer(
  http.get("https://crm.example.test/v2/contacts", ({ request }) => {
    const cursor = new URL(request.url).searchParams.get("cursor");
    return HttpResponse.json(cursor === "c2" ? page2 : page1);
  }),
);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterAll(() => server.close());

test("listContacts follows the cursor to the last page", async () => {
  const client = new CrmClient("https://crm.example.test", "test-key");
  const contacts = await client.listContacts();
  expect(contacts).toHaveLength(page1.data.length + page2.data.length);
});

The fake server returns the second page only to a request with the cursor, so a client that stops after the first page fails this test.

FAQ

Is it worth unit testing an API client?

Yes, an API client you wrote is worth testing against a fake HTTP server with recorded responses, because parsing, paging and error mapping are where clients fail. A test that mocks the HTTP library call by call is not worth writing, because it repeats the client's code and never sees a real response.

Should I write tests for a REST API wrapper?

Yes, test a REST API wrapper for each call your product makes: one recorded success response, each error status the wrapper handles, and each paged list. Skip the endpoints your product never calls.

Should my unit tests call the real API when testing a wrapper for it?

No, tests in the build should not call the real API, because its rate limits and outages fail builds that have nothing wrong in your code. Send the same requests to the provider's sandbox from a separate daily job instead.