Tests need credential-shaped values. Using a real key in a test fixture is how real keys reach public repositories, so this builds headers that look right and authenticate to nothing.
Test the rejection paths
Most authentication test suites cover one case: a valid credential succeeds. The cases that actually break in production are the others, and each needs a distinct header to exercise:
| Case | Header | Expected |
|---|---|---|
| No credential | *(omit)* | 401 with WWW-Authenticate |
| Wrong scheme | Basic <token> | 401 |
| Malformed | Bearer with no value | 401 |
| Unknown credential | Bearer invalid | 401 |
| Expired token | a token past exp | 401 |
| Valid but under-scoped | valid token, wrong scope | 403, not 401 |
That last row is the one most often wrong. A scope failure is 403 — the caller is authenticated, they are simply not permitted. Returning 401 sends well-behaved clients into a refresh loop that can never succeed.
Distinguish 401 from 403 in tests
test("refreshes once on 401, then gives up", async () => {
fetchMock
.mockResolvedValueOnce(new Response(null, { status: 401 }))
.mockResolvedValueOnce(new Response("{}", { status: 200 }));
await client.get("/resource");
expect(refreshToken).toHaveBeenCalledTimes(1);
});
test("does not retry a 403", async () => {
fetchMock.mockResolvedValue(new Response(null, { status: 403 }));
await expect(client.get("/resource")).rejects.toThrow();
expect(refreshToken).not.toHaveBeenCalled();
});Never use a real key in a fixture
Even an expired one, even a test-environment one. Test fixtures get committed, and a scanner cannot tell that your sk_test_ key is harmless. Worse, the habit means that one day a sk_live_ key gets pasted into the same place.
Generate values here, or use the provider format generator when the shape needs to match a specific vendor.
Basic auth in tests
Basic encodes username:password as Base64. When an API uses the key-as-username convention, the trailing colon matters: sk_test_abc: and sk_test_abc produce different Base64, and forgetting it produces a test that fails for a reason unrelated to what it is testing.
Frequently asked questions
Should tests use a real token from the auth server?
Integration tests, yes — that path is worth exercising. Unit tests, no: they should not depend on a network service. Mint a token locally with the OAuth token generator and stub the verification.
How do I test token expiry without waiting?
Generate a token with an exp in the past, or inject a clock into your validator so tests can advance time. The expiry calculator produces the timestamps.
What should a 401 response include?
A WWW-Authenticate header naming the scheme, and a body that does not reveal whether the credential exists. "Invalid credentials" for both unknown and wrong keys denies an attacker a key-enumeration oracle.
Is it safe to commit these generated headers?
Yes. They are random values that authenticate to nothing. That is the point — they are safe where a real key is not.