How to Test and Mock the Cohere API

Every test that calls the live Cohere API is a test that can fail because of someone else's deploy. Here is where to draw the line between mocked and real — and what each side is actually allowed to tell you.

11 min read
Staff Pick

📡 Monitor your APIs — know when they go down before your users do

Better Stack checks uptime every 30 seconds with instant Slack, email & SMS alerts. Free tier available.

Start Free →

Affiliate link — we may earn a commission at no extra cost to you

The first integration test anyone writes against Cohere calls the real endpoint, asserts the model said something sensible, and passes. It keeps passing for about three weeks. Then it fails on a Sunday because of a rate limit, fails again on a fork's pull request because the key is not there, fails a third time because the model phrased its answer differently — and someone adds `retry: 3` and stops reading the output.

The short version

Mock the network, not the SDK. Record fixtures from one real call rather than hand-writing them. Spend most of your testing effort on the failure responses, because those are the paths that only ever run during an incident. And keep one live check outside CI — a suite full of mocks cannot tell you Cohere is down.

Why CI Should Not Call Cohere Directly

None of these are theoretical. Each one is a specific way a live-calling test suite degrades into a suite nobody trusts.

  • Your build inherits their uptime. A Cohere incident becomes a red build across every branch, and the team learns that red means "probably upstream" — which is exactly the habit that lets a real regression through.
  • Every pull request costs money. Token spend scales with contributor activity rather than with anything you would choose to pay for, and it is invisible until the invoice.
  • Secrets have to exist everywhere CI runs. That includes fork pull requests, where a real CO_API_KEY either fails to exist (so external contributors can never get a green build) or does exist (which is a far worse problem).
  • Model output is not deterministic. Asserting on generated prose produces failures that are not bugs, and the standard fix — loosening the assertion until it always passes — leaves a test that costs money and proves nothing.
  • Rate limits look exactly like flakiness. A parallelised suite trips Cohere's limits precisely because it is fast, and the 429 arrives as an intermittent failure rather than as a clear message about concurrency.

The Four Layers, and What Each One Proves

Most teams have layer one and layer four missing, then wonder why a green suite did not prevent the outage. Each layer answers a different question, and no layer answers another layer's.

LayerRunsProvesCannot tell you
Unit / mockedEvery pushYour prompt construction, routing, parsing, retry and error-handling logicNothing about the provider itself
Recorded fixturesEvery pushThat your code survives real response bodies, including error bodiesWhether those bodies are still current
Live smoke testPre-deploy, or nightlyCredentials work, model ids resolve, the contract still holdsBehaviour under real production load
Synthetic monitoringContinuouslyThat the provider is up and fast right now, from outside your appNothing — this is the layer that pages you

The line worth internalising: layers one and two are about your code, layer three is about your assumptions, and layer four is about Cohere. Adding more of the first two will never substitute for the last one.

A Mock You Can Paste In

Cohere is its own — not OpenAI-shaped, and most codebases reach it through the `cohere-ai` client. Intercept at the HTTP boundary so the real client still runs — its validation, its error classes, its stream parsing all stay in the test.

import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';

const server = setupServer(
  http.post('https://api.cohere.com/v2/chat', () =>
    HttpResponse.json(SUCCESS_FIXTURE)),
);

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

// onUnhandledRequest: 'error' is the important line. Without it, a call your
// code makes to an endpoint you forgot to stub goes to the real api.cohere.com
// with whatever key is in the environment.

Get the success fixture by making one real call and saving the response verbatim, not by writing out what you think the body looks like. A hand-authored fixture tests your code against your own assumptions, which it will always pass.

Simulate the Four Failures That Actually Happen

This is where mocking earns its cost. Every one of these paths runs for the first time during an incident unless a test made it run earlier.

// 1. Rate limited — assert the backoff reads retry-after.
http.post('https://api.cohere.com/v2/chat', () =>
  new HttpResponse(null, { status: 429, headers: { 'retry-after': '2' } }));

// 2. Server error — assert a bounded retry, then a clean surfaced failure.
http.post('https://api.cohere.com/v2/chat', () =>
  new HttpResponse(null, { status: 500 }));

// 3. Timeout — assert your own deadline fires before the user's patience does.
http.post('https://api.cohere.com/v2/chat', async () => {
  await new Promise((r) => setTimeout(r, 30_000));
  return HttpResponse.json(SUCCESS_FIXTURE);
});

// 4. Truncated stream — the one nobody tests, and the one that
//    quietly writes half an answer into your database as if it were whole.
http.post('https://api.cohere.com/v2/chat', () => {
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue(new TextEncoder().encode(
        'data: {"choices":[{"delta":{"content":"The ans"}}]}\n\n'));
      controller.close(); // no [DONE], no finish_reason
    },
  });
  return new HttpResponse(stream, {
    headers: { 'Content-Type': 'text/event-stream' },
  });
});

The truncated stream is the expensive one

A stream that ends without a terminator is not an error at the HTTP layer — the status was 200 and bytes arrived. Code that accumulates deltas and writes the result on stream-close will happily store half an answer and mark it complete. Assert that a stream ending without a finish signal is treated as a failure, not as a short response.

The Cohere Trap: A Fixture Recorded Against the Wrong API Version

Cohere's chat API is not OpenAI-shaped, and it has more than one generation in active use. The v1 and v2 chat surfaces differ in where the message goes, how the assistant's reply is nested, and what an error body looks like. A fixture copied from a blog post, an old integration test, or an LLM's memory of the SDK will frequently be the wrong generation — and because a mock never validates itself, that mismatch survives right up until a real call is made.

This makes the recording step matter more here than anywhere else on this list. Do not hand-write a Cohere response body. Make one real call with the client version you actually have installed, save the response verbatim, and use that as the fixture. Hand-authored fixtures encode what you believed the API looked like; recorded ones encode what it is.

The same applies to errors. Cohere's error payloads do not mirror the OpenAI error envelope, so if your handler was written by pattern-matching a generic `error.error.message` path, a hand-written fixture that happens to use that shape will validate a handler that cannot read a real Cohere failure. Record a genuine 401 and a genuine 429 once, keep them, and let the handler be tested against the real thing.

Record the fixture instead of writing it

// scripts/record-cohere-fixture.ts — run once, by hand, with a real key.
import { CohereClientV2 } from 'cohere-ai';
import { writeFileSync } from 'node:fs';

const cohere = new CohereClientV2({ token: process.env.CO_API_KEY });

const res = await cohere.chat({
  model: 'command-a-03-2025',
  messages: [{ role: 'user', content: 'Reply with the single word: ok' }],
});

// Verbatim. Do not tidy it, do not reshape it into OpenAI's envelope.
writeFileSync(
  'test/fixtures/cohere-chat-success.json',
  JSON.stringify(res, null, 2),
);

// Repeat once with a deliberately bad token to capture the real 401 body.

The One Test That Should Hit the Real API

Keep exactly one, keep it out of the pull-request path, and give it a narrow job: prove that the credentials work, that the model id still resolves, and that the response still has the shape your fixtures claim. It is the only thing standing between a fully mocked suite and a fixture that has quietly stopped describing reality.

// One test. Runs pre-deploy or nightly, never on a pull request.
// Its job is to prove the assumptions the mocks are built on.
const res = await fetch('https://api.cohere.com/v2/chat', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'command-a-03-2025',
    messages: [{ role: 'user', content: 'Reply with the single word: ok' }],
    max_tokens: 5,
  }),
});

if (!res.ok) {
  throw new Error(`Cohere smoke test failed: ${res.status}`);
}

// Assert the SHAPE the fixtures encode, not the words that came back.
const body = await res.json();
assertMatchesFixtureShape(body);

Run it on a schedule and before deploy. Do not run it on pull requests, because that reintroduces every problem in the first section — the secret exposure, the fork failures, the upstream-outage red builds — in exchange for a signal you already get nightly.

Where Testing Stops and Monitoring Starts

A test suite runs when you push. An outage happens when it happens. That gap is the entire reason a green pipeline and a broken product coexist so comfortably: nothing in CI is watching Cohere at 02:00 on a Saturday, and a nightly smoke test tells you about a failure up to twenty-four hours after your users found it.

The layer that closes the gap is independent probing — real requests to api.cohere.com on an interval, from outside your infrastructure, alerting on both errors and latency. It is also what answers the question your team will actually ask during an incident, which is never "did the tests pass" but always "is it us or is it them?"

Frequently Asked Questions

Should my CI pipeline call the live Cohere API?

Almost never on every push. Live calls in CI make your build depend on someone else's uptime, cost money on every pull request, leak a real CO_API_KEY into every fork and pull-request runner, and produce non-deterministic failures that teams eventually silence by retrying. Keep the pull-request path fully mocked, and run a small live smoke test on a schedule or before deploy where a failure means something and a secret can be handled properly.

What is the difference between mocking the Cohere SDK and mocking the HTTP layer?

Mocking the SDK replaces the client object, so your test never exercises the client's own validation, error classes or stream parsing — the parts most likely to change under you. Mocking the HTTP layer (msw, nock, a local server, or a base-URL override) intercepts the request to api.cohere.com and lets the real client run against a fake network. The second is more work to set up once and catches an entire category of bug the first cannot see, including SDK upgrades that change behaviour silently.

How do I test what my code does when Cohere returns a 429 or a 500?

Make the mock return them. This is the single highest-value thing a mock does, because the failure paths are the ones that are never exercised by hand and are always the ones that break in an incident. Return a 429 with a retry-after header and assert the backoff honours it rather than sleeping a hardcoded amount; return a 500 and assert the request is retried a bounded number of times; return a stream that ends mid-message and assert the partial output is not silently persisted as complete.

Why do my hand-written Cohere fixtures pass tests but fail in production?

Because a hand-written fixture is a statement about what you think the API returns, and the test only checks that your code can read your own belief. Cohere's chat response is not shaped like OpenAI's, and it differs between the v1 and v2 surfaces, so a fixture assembled from memory or from an older example encodes a shape the live endpoint never sends. Record fixtures from one real call against the client version you have installed — including the error responses — and the class of bug disappears.

If everything is mocked, how do I know when Cohere is actually down?

You do not, and that is the honest limit of a test suite: mocks tell you your code is correct, not that the provider is available. Availability is a monitoring question, not a testing one. Something outside your application has to be making real requests to api.cohere.com on an interval and alerting when they fail or slow down — otherwise the first signal of an outage is a user, and the first thing your team will do is spend twenty minutes deciding whether the bug is yours.

Related Guides

Your Tests Cannot Page You at 2am

Mocks prove your code is right. They cannot tell you Cohere stopped answering an hour ago. API Status Check probes Cohere and the rest of your stack independently and alerts on errors and latency, so "is it us or is it them?" has an answer before anyone opens a terminal.

Start Your Free Trial →

🛠 Tools We Use & Recommend

Tested across our own infrastructure monitoring 200+ APIs daily

Better StackBest for API Teams

Uptime Monitoring & Incident Management

Used by 100,000+ websites

Monitors your APIs every 30 seconds. Instant alerts via Slack, email, SMS, and phone calls when something goes down.

We use Better Stack to monitor every API on this site. It caught 23 outages last month before users reported them.

Free tier · Paid from $24/moStart Free Monitoring
1PasswordBest for Credential Security

Secrets Management & Developer Security

Trusted by 150,000+ businesses

Manage API keys, database passwords, and service tokens with CLI integration and automatic rotation.

After covering dozens of outages caused by leaked credentials, we recommend every team use a secrets manager.

SEMrushBest for SEO

SEO & Site Performance Monitoring

Used by 10M+ marketers

Track your site health, uptime, search rankings, and competitor movements from one dashboard.

We use SEMrush to track how our API status pages rank and catch site health issues early.

From $129.95/moTry SEMrush Free
View full comparison & more tools →Affiliate links — we earn a commission at no extra cost to you