Tutorials / API & Services

Retry-safe API testing

A blanket retry hides real bugs behind flaky infrastructure. Test Junkie's retry_on and no_retry_on let a test retry only on the exception types you name - so a genuine assertion failure about the response still fails on the first try, the way it should.

test_httpbin_contract.pypython
import requests
from test_junkie.decorators import Suite, test

@Suite()
class HttpBinContractSuite:

    # only a transient network error is retried - up to twice
    @test(retry=2, retry_on=[requests.exceptions.ConnectionError, requests.exceptions.Timeout])
    def test_status_endpoint_is_reachable(self):
        response = requests.get("https://httpbin.org/status/200", timeout=5)
        assert response.status_code == 200

    @test(retry=2, retry_on=[requests.exceptions.ConnectionError, requests.exceptions.Timeout])
    def test_response_json_has_expected_shape(self):
        response = requests.get("https://httpbin.org/get", timeout=5)
        data = response.json()
        # AssertionError isn't in retry_on, so a wrong shape fails
        # once, honestly, instead of being masked by a retry
        assert "url" in data
Why not just retry=2 on its own?
Plain retry=2 retries on any exception, assertion failures included - so a real contract break gets silently re-run and might pass by luck, or just burns time failing the same way twice. Naming the exception types in retry_on keeps retries scoped to actual infrastructure flakiness.

The inverse: retry on everything except one thing

Sometimes the exception you want to exclude is easier to name than the ones you want to catch. no_retry_on flips the list to a blocklist instead of an allowlist:

test_httpbin_contract.pypython
@test(retry=2, no_retry_on=[AssertionError])
def test_status_endpoint_is_reachable(self):
    # retried on connection errors, timeouts, DNS failures, etc -
    # just never on the assertion itself
    response = requests.get("https://httpbin.org/status/200", timeout=5)
    assert response.status_code == 200

This is one concept, applied to one real API - see the full retry reference for suite-level retries, and parameters for retrying only the parameter sets that actually failed.