如何在 Mock 中為已退役資源區分 410 Gone 與一般 404
Mocking a 410 Gone for a Retired Resource vs a Plain 404
為已退役且不會恢復的資源建立 Mock 時,應回傳 410 Gone,而非一般 404,讓客戶端知道資源已永久移除並停止重試。410 回應宜附上説明及替代連結;退役流程可先發出 Deprecation 和 Sunset 標頭,截止後回傳 410。測試亦應確認客戶端不會重試,並能顯示替代位置。
Quick answer: A 410 Gone tells a client that a resource used to exist but was deliberately removed and will not come back, while a 404 means the server cannot find anything at that URL. When you mock a retired endpoint, return 410 with an explicit body and, where possible, point the client at the replacement. Mocking it as a 404 blurs the distinction and lets client code keep retrying a resource that is permanently gone.
Why the difference matters
Clients treat the two codes differently. A 404 can be transient: a deployment might be in progress, a path could be misspelled, or a record may simply be absent. A 410 is a deliberate statement that the resource existed and has been retired, so a well-behaved client should stop polling and surface a permanent message. If the mock returns 404 for a retired resource, the client's retry logic may keep hammering an endpoint that the team has already removed.
| Code | Meaning | Expected client behavior |
|---|---|---|
| 404 Not Found | No resource at this URL | May retry or treat as absent |
| 410 Gone | Resource was retired permanently | Stop retrying, show permanent notice |
| 301/308 redirect | Resource moved to a new URL | Follow the new location |
Mocking the retired endpoint
A 410 stub should be explicit and carry enough context for the client to explain the situation and, when one exists, name the replacement. Here is a mock configuration for a retired report endpoint.
The body does more than mark the endpoint dead. It gives the client a stable problem type and a replacement URL, so an SDK can log a useful warning or even guide a migration rather than failing with an opaque code.
Pairing 410 with a deprecation window
The honest lifecycle is staged. First the endpoint emits a Deprecation and Sunset header while still working, then it returns 410 after the cutoff, and eventually the stub itself can be removed. Mocking all three phases lets you test that the client handles the warning, the permanent failure, and the final absence in order, rather than discovering the behavior in production when the endpoint is switched off.
// Phase 1: still works, but announces retirement Deprecation: date="Sun, 01 Jun 2026 00:00:00 GMT" Sunset: Wed, 02 Sep 2026 00:00:00 GMT Link: <https://example.com/migration>; rel="sunset"
What the client should do
A robust client distinguishes the codes in its error handling. On 410 it should not schedule a retry, should cache the permanent result if the call is repeated, and should surface the replacement when one is provided. This is a piece of behavior worth testing against the mock, because a client that lumps 404 and 410 together will keep retrying a retired resource and generate noise in the logs and the rate-limit budget.
How an agent gets it wrong
An agent that is asked to simulate a removed endpoint often reaches for 404 because it is the generic "not there" code, without recognizing that 410 carries intent. When reviewing generated mocks, check whether the retired endpoint returns 410 and whether the client test asserts that no retry occurs. If the agent's mock uses 404, ask it to model the full deprecation lifecycle instead, including the replacement, so the test reflects the real contract rather than a vague absence.
Keeping mocks aligned with the contract
A spec-first workflow makes the distinction harder to lose. Mark the retired operation in the OpenAPI document with a deprecation flag and, after sunset, document the 410 response explicitly so the generated mock returns it automatically. Hand-written mocks tend to drift as people forget why an endpoint was returning a particular code, while a contract keeps the permanent-failure semantics attached to the operation that was actually retired.
Observing the retired endpoint in practice
Once the 410 stub is running, verify the client's behavior rather than just the response code. Point the real client at the mock, trigger the retired call, and confirm that it does not schedule a retry, that it surfaces the permanent message, and that any replacement URL is shown or logged. These are observable differences that a status code alone does not prove. If the client still retries, the bug is in the client's error handling, and the mock is doing its job by exposing it before the production endpoint is switched off.
Watching the client's request count against the mock is also a useful check: a correct 410 handler produces one call, while a client that misreads it as a transient 404 will show several attempts. That single metric tells you whether the distinction is being honored.
Frequently asked questions
When should I use 410 instead of 404?
Use 410 when a resource existed, was deliberately retired, and will not return, so clients know to stop retrying. Use 404 for a generic absence that may be temporary or due to a wrong path.
Should a 410 response have a body?
Yes, ideally. A problem-details body can explain the retirement, give a cutoff date, and point to a replacement, which is far more useful than an empty response.
Do clients actually behave differently for 410?
Well-built clients do: they avoid retries and surface a permanent message. If your client currently treats 404 and 410 the same, that is exactly the behavior the mock should help you fix.
How long should a 410 stub stay in place?
Keep it through the migration window while clients still reference the old URL, then remove it once the deprecation period ends and traffic to the endpoint has fallen to zero.
This is part of the The API Mocking Guide — start there for the full set of patterns for honest API mocks.
來源:DEV Community · dev.to