{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/python/testing-http-requests/",
  "description": "Test HTTP requests in Python with pytest-httpx for full mocking, respx for pattern matching, or VCR.py for recording real responses.",
  "path": "/python/testing-http-requests/",
  "publishedAt": "2024-09-02T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Python",
    "API",
    "Testing",
    "TIL"
  ],
  "textContent": "Here's a Python snippet that makes an HTTP POST request:\n\nThe function make_request makes an async HTTP request with the [HTTPx] library. Running\nthis with asyncio.run(make_request(\"https://httpbin.org/post\")) gives us the following\noutput:\n\nWe're only interested in the json field and want to assert in our test that making the\nHTTP call returns the expected values.\n\nTesting the HTTP request\n\nNow, how would you test it? One approach is by patching the httpx.AsyncClient instance to\nreturn a canned response and asserting against that. The happy path might be tested as\nfollows:\n\nThat's quite a bit of work just to test a simple HTTP request. The mocking gets pretty hairy\nas the complexity of your HTTP calls increases. One way to cut down the mess is by using a\nlibrary like [respx] that handles the patching for you.\n\nSimplifying mocks with respx\n\nFor instance:\n\nMuch cleaner. During tests, respx intercepts HTTP requests made by httpx, allowing you to\ntest against canned responses. The library provides a context manager that acts like an\nhttpx client, so you can set the expected response. This removes the need to manually patch\nmethods like post in httpx.AsyncClient.\n\nTesting with a stub client\n\nThe previous strategy wouldn't work if you want to change your HTTP client since respx is\ncoupled with httpx. As an alternative, you could rewrite make_request to parametrize the\nHTTP client, pass a stub object during the test, and assert against it. This eliminates the\nneed to write fragile mocking sludges or depend on an external mocking library.\n\nHere's how you'd change the code:\n\nNow the tests would look as follows:\n\nMuch better!\n\nIntegration testing with a test server\n\nOne thing I've picked up from writing Go is that it's often just easier to perform\nintegration tests on these I/O-bound functions. That is, you can spin up a server that\nreturns a canned response and then test your code against it to assert if it's getting the\nexpected output.\n\nThe test could look as follows. This assumes make_request takes in an AsyncClient\ninstance as a parameter, as shown in the last example.\n\nIn the above test, we're using [Starlette] to define a simple ASGI server that returns our\nexpected response. Then we set up the httpx.AsyncClient so it makes the request against\nthe test server instead of making an external network call. Finally, we call the\nmake_request function and assert the expected payload.\n\nSure, you could set up the server with the standard library's http module, but that code\ndoesn't look half as pretty.\n\n\n\n\n[httpx]:\n    https://www.python-httpx.org/\n\n[respx]:\n    https://lundberg.github.io/respx/\n\n[starlette]:\n    https://www.starlette.io/",
  "title": "Shades of testing HTTP requests in Python"
}