
A successful response can conceal a broken retry policy. A client may recover from a temporary failure while issuing more requests than intended. It may also repeat an operation that the server has already performed. A small local stub gives you control over the response sequence, so you can look at the attempts as well as the final outcome.
Choose a read operation for the first test
Use a disposable endpoint such as GET /report/42. Decide what the client should do if the first response is unavailable and the second succeeds. Write the expectation before running anything. For example, the caller should receive the report and the stub should see exactly two requests with the same path. Treat that as a test expectation for your own client configuration.
Configure the stub to answer the first request with 503 and the next with 200 and a small JSON body. Record every request at the stub, including the path and an identifier you can use to group attempts. Give each test case its own stub state so a successful request from a previous case cannot consume the response meant for the current one. A scenario counter should reset between cases, and parallel cases should use separate ports or separate scenario identifiers.
Assert the behavior that matters to the caller
First check the returned value. Then check the number of requests and their destination. A client that retries several times may still return the right report. Looking only at the result hides the excess traffic. If your client exposes attempt information to callers, verify that the test can distinguish one logical request from the two attempts that served it.
Use retry behavior comparison criteria to separate eventual success, request count, handling of permanent errors and visibility in logs. The criteria make it easier to compare two libraries without giving a single pass mark to every client that returns the report. Record the configured policy with the outcome, since a difference may come from configuration or middleware as well as the client library itself.
Give the stub an error that should end the operation
Change the script so the first response is 400, then leave a success response queued. If your policy treats the first error as permanent, expect the error to reach the caller and verify that the success response was never used. Check whether the original error details remain available. A client that wraps every failure in the same message can make it difficult to diagnose a bad request.
Next return only temporary failures. Set the expected maximum attempts and check what happens when they are exhausted. Distinguish a setting that counts retries from one that counts all attempts. An initial request plus two retries gives three requests at the stub. Put that number in the assertion so a configuration change cannot silently alter the test.
Check timing without relying on a narrow wall clock window
If the client lets you inject a clock or delay function, record requested delays and advance time under test control. This makes the test useful even when a busy machine schedules work later than expected. For randomized delays, assert the permitted range or inject a fixed source of randomness. If timing can only be observed through the stub, record approximate intervals and allow for scheduling noise.
Try cancellation during the wait before the next attempt. Confirm that the caller receives the cancellation outcome and the stub does not see another request after cancellation. Make sure the test controls when cancellation happens. Cancelling before the first response arrives exercises a different path from cancelling during the retry wait.
Keep writes in a separate scenario
If you also test a write, use a disposable endpoint and a stub that records the effect independently of the HTTP response. Simulate the server accepting the write while the client loses the response. Repeat with the mechanism your API actually uses to recognize a repeated operation. Verify the number of effects separately from the number of requests. If the API cannot safely identify a repeat, document the policy that applies to that write before enabling retries.
Save the response scripts and expected attempt counts with the tests. When you change a timeout, library or middleware layer, rerun the same cases and inspect the first point where they differ. You will have a record of what the caller saw and what the service actually received.
Verify the stub itself
Run one request directly against a fresh stub before using the client. Confirm that the first response is the intended failure and that the request log records it. Reset the scenario before the actual client test. If the direct request cannot reach the stub, check the local port and route before interpreting the result as a retry failure.
