[{"data":1,"prerenderedAt":91},["ShallowReactive",2],{"qa-\u002Ffastapi\u002Ftesting\u002Ftest-client":3},{"page":4,"siblings":82,"blog":73},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":72,"related":73,"seo":74,"seoDescription":75,"stem":76,"subtopic":77,"topic":78,"topicSlug":79,"updated":80,"__hash__":81},"qa\u002Ffastapi\u002Ftesting\u002Ftest-client.md","Test Client",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","FastAPI","fastapi",{},true,1,"\u002Ffastapi\u002Ftesting\u002Ftest-client",[23,28,32,36,40,44,48,52,56,60,64,68],{"id":24,"difficulty":25,"q":26,"a":27},"testclient-basics","easy","What is `TestClient` in FastAPI and how do you use it?","`TestClient` (from `starlette.testclient`) wraps your FastAPI app in a\n`requests`-compatible interface. It runs the full ASGI stack synchronously\nso you can test endpoints without starting a real server.\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi.testclient import TestClient\n\napp = FastAPI()\n\n@app.get(\"\u002Fitems\u002F{id}\")\nasync def get_item(id: int):\n    return {\"id\": id}\n\nclient = TestClient(app)\n\ndef test_get_item():\n    response = client.get(\"\u002Fitems\u002F42\")\n    assert response.status_code == 200\n    assert response.json() == {\"id\": 42}\n```\n\nRule of thumb: create `TestClient` once per module (or as a pytest fixture)\nrather than on every test — instantiation is cheap but consistent fixture\nscope is cleaner.\n",{"id":29,"difficulty":14,"q":30,"a":31},"testclient-lifespan","How do you ensure lifespan events (startup\u002Fshutdown) run in tests?","Use `TestClient` as a **context manager**:\n\n```python\nfrom fastapi.testclient import TestClient\nfrom app.main import app\n\ndef test_with_lifespan():\n    with TestClient(app) as client:\n        # startup has run (DB connected, app.state populated)\n        response = client.get(\"\u002Fhealth\")\n        assert response.status_code == 200\n    # shutdown has run (DB disconnected)\n```\n\nWithout the `with` block, lifespan events don't run, and any code that depends\non `app.state` will fail.\n\nRule of thumb: always use `with TestClient(app) as client:` in tests that need\nstartup resources; plain `TestClient(app)` is fine for stateless routes.\n",{"id":33,"difficulty":25,"q":34,"a":35},"pytest-fixture-client","How do you set up a pytest fixture for a FastAPI `TestClient`?","```python\nimport pytest\nfrom fastapi.testclient import TestClient\nfrom app.main import app\n\n@pytest.fixture(scope=\"module\")\ndef client():\n    with TestClient(app) as c:\n        yield c\n\ndef test_list_items(client):\n    resp = client.get(\"\u002Fitems\")\n    assert resp.status_code == 200\n\ndef test_create_item(client):\n    resp = client.post(\"\u002Fitems\", json={\"name\": \"Widget\", \"price\": 9.99})\n    assert resp.status_code == 201\n```\n\n`scope=\"module\"` starts the app once per module, which is faster than per-test.\nUse `scope=\"function\"` if tests mutate state that must be reset.\n\nRule of thumb: use `scope=\"module\"` for read-heavy test files; use `scope=\"function\"`\nfor tests that write to a database or modify `app.state`.\n",{"id":37,"difficulty":25,"q":38,"a":39},"json-body-test","How do you send a JSON body in a TestClient POST request?","Pass the `json=` keyword argument — TestClient serialises it and sets\n`Content-Type: application\u002Fjson` automatically:\n\n```python\ndef test_create_item(client):\n    response = client.post(\n        \"\u002Fitems\",\n        json={\"name\": \"Widget\", \"price\": 9.99},\n    )\n    assert response.status_code == 201\n    data = response.json()\n    assert data[\"name\"] == \"Widget\"\n    assert \"id\" in data\n```\n\nFor raw string bodies: `data='{\"name\":\"Widget\"}'` with `headers={\"Content-Type\": \"application\u002Fjson\"}`.\n\nRule of thumb: always use `json=` (not `data=`) for JSON payloads — it handles\nserialisation correctly and sets the content type header.\n",{"id":41,"difficulty":25,"q":42,"a":43},"headers-test","How do you send custom headers (e.g., Authorization) in TestClient?","Pass `headers=` dict to the request method:\n\n```python\ndef test_authenticated(client):\n    token = create_test_token(user_id=1)\n    response = client.get(\n        \"\u002Fme\",\n        headers={\"Authorization\": f\"Bearer {token}\"},\n    )\n    assert response.status_code == 200\n```\n\nOr set default headers on the client itself:\n```python\n@pytest.fixture\ndef auth_client(client):\n    token = create_test_token(user_id=1)\n    client.headers.update({\"Authorization\": f\"Bearer {token}\"})\n    return client\n```\n\nRule of thumb: create a separate `auth_client` fixture for tests that always\nneed auth headers — it avoids repeating the header on every request.\n",{"id":45,"difficulty":25,"q":46,"a":47},"query-params-test","How do you test query parameters with TestClient?","Pass `params=` dict:\n\n```python\ndef test_search(client):\n    response = client.get(\"\u002Fitems\", params={\"q\": \"widget\", \"page\": 2, \"size\": 10})\n    assert response.status_code == 200\n    # equivalent to GET \u002Fitems?q=widget&page=2&size=10\n```\n\nFor multi-value query params:\n```python\nresponse = client.get(\"\u002Fitems\", params=[(\"tags\", \"python\"), (\"tags\", \"web\")])\n# GET \u002Fitems?tags=python&tags=web\n```\n\nRule of thumb: use `params=` dict for simple query strings; use a list of tuples\nwhen the same key appears multiple times.\n",{"id":49,"difficulty":14,"q":50,"a":51},"cookies-test","How do you send cookies and test cookie-based auth with TestClient?","Set cookies on the client or per-request:\n\n```python\n# Per-request\ndef test_cookie_auth(client):\n    response = client.get(\"\u002Fprofile\", cookies={\"session\": \"valid-session-token\"})\n    assert response.status_code == 200\n\n# Persistent across requests (simulates browser session)\nclient.cookies.set(\"session\", \"valid-session-token\")\nresponse = client.get(\"\u002Fprofile\")\n```\n\nTestClient automatically carries `Set-Cookie` headers between requests\nwhen used as a context manager — simulating a browser:\n\n```python\nwith TestClient(app) as client:\n    client.post(\"\u002Flogin\", data={\"username\": \"alice\", \"password\": \"secret\"})\n    # client now has the session cookie\n    response = client.get(\"\u002Fprofile\")\n    assert response.status_code == 200\n```\n\nRule of thumb: use the `with TestClient(app)` context manager for tests that\nsimulate a full login → use → logout flow.\n",{"id":53,"difficulty":25,"q":54,"a":55},"form-data-test","How do you test form data submission with TestClient?","Pass `data=` (not `json=`) for URL-encoded form data:\n\n```python\ndef test_login_form(client):\n    response = client.post(\n        \"\u002Ftoken\",\n        data={\"username\": \"alice\", \"password\": \"secret\", \"grant_type\": \"password\"},\n    )\n    assert response.status_code == 200\n    assert \"access_token\" in response.json()\n```\n\nFor multipart file upload:\n```python\ndef test_file_upload(client):\n    response = client.post(\n        \"\u002Fupload\",\n        files={\"file\": (\"report.csv\", b\"id,name\\n1,Alice\", \"text\u002Fcsv\")},\n    )\n    assert response.status_code == 200\n```\n\nRule of thumb: use `data=` for form fields, `files=` for file uploads; never\nmix `json=` with `data=` in the same request.\n",{"id":57,"difficulty":14,"q":58,"a":59},"assert-validation-error","How do you test that FastAPI returns 422 for invalid input?","Send an intentionally malformed request and assert on the 422 status code and\nerror detail structure:\n\n```python\ndef test_invalid_price(client):\n    response = client.post(\"\u002Fitems\", json={\"name\": \"Widget\", \"price\": \"not-a-number\"})\n    assert response.status_code == 422\n    errors = response.json()[\"detail\"]\n    assert any(e[\"loc\"] == [\"body\", \"price\"] for e in errors)\n    assert any(e[\"type\"] == \"float_parsing\" for e in errors)\n```\n\nCheck `loc` (where the error is) and `type` (what kind of error) rather than\nthe `msg` string — messages can change between Pydantic versions.\n\nRule of thumb: assert on `detail[*].loc` and `detail[*].type` for validation\nerrors — these are stable; `msg` wording changes between library versions.\n",{"id":61,"difficulty":14,"q":62,"a":63},"raise-on-server-error","What is `raise_server_exceptions` and when should you disable it?","`TestClient` re-raises server-side exceptions by default (`raise_server_exceptions=True`).\nThis turns a 500 response into a Python exception in the test — useful for\ndebugging but unhelpful when you're testing error handling.\n\n```python\n# Test that a 500 is returned without raising\nclient = TestClient(app, raise_server_exceptions=False)\n\ndef test_internal_error():\n    response = client.get(\"\u002Fbroken\")\n    assert response.status_code == 500\n```\n\nOr use a context manager:\n```python\nwith TestClient(app, raise_server_exceptions=False) as client:\n    resp = client.get(\"\u002Fbroken\")\n    assert resp.status_code == 500\n```\n\nRule of thumb: keep the default (`True`) for development tests so unhandled\nexceptions surface immediately; set `False` when specifically testing error-handling middleware.\n",{"id":65,"difficulty":25,"q":66,"a":67},"test-redirect-follow","Does TestClient follow redirects by default and how do you control this?","`TestClient` follows redirects by default (`follow_redirects=True`). To test\nthat a redirect is issued without following it:\n\n```python\ndef test_redirect(client):\n    response = client.get(\"\u002Fold-path\", follow_redirects=False)\n    assert response.status_code == 301\n    assert response.headers[\"location\"] == \"\u002Fnew-path\"\n```\n\nTo disable redirect following globally:\n```python\nclient = TestClient(app, follow_redirects=False)\n```\n\nRule of thumb: disable `follow_redirects` when testing the redirect itself\n(status code + Location header); keep it enabled when you care about the\nfinal destination response.\n",{"id":69,"difficulty":14,"q":70,"a":71},"dependency-override-test","How do you replace a database dependency with an in-memory fake in tests?","Use `app.dependency_overrides`:\n\n```python\nfrom app.main import app\nfrom app.db import get_db\n\ndef fake_get_db():\n    db = FakeDatabase()\n    db.users = [{\"id\": 1, \"name\": \"Alice\"}]\n    yield db\n\napp.dependency_overrides[get_db] = fake_get_db\n\nclient = TestClient(app)\n\ndef test_list_users():\n    resp = client.get(\"\u002Fusers\")\n    assert resp.status_code == 200\n    assert len(resp.json()) == 1\n```\n\nAlways clean up after the test:\n```python\n@pytest.fixture(autouse=True)\ndef reset_overrides():\n    yield\n    app.dependency_overrides.clear()\n```\n\nRule of thumb: put `app.dependency_overrides.clear()` in an `autouse` fixture\nteardown — stale overrides in one test silently corrupt subsequent tests.\n",12,null,{"description":11},"FastAPI TestClient interview questions — requests-style testing, fixtures, status codes, headers, cookies and testing error responses.","fastapi\u002Ftesting\u002Ftest-client","TestClient","Testing","testing","2026-06-20","IleoACMiE0Rdhnr9eoEdzZ9DlwJxNrNr7NnNqdzKO-s",[83,84,87],{"subtopic":77,"path":21,"order":20},{"subtopic":85,"path":86,"order":12},"Async Testing","\u002Ffastapi\u002Ftesting\u002Fasync-testing",{"subtopic":88,"path":89,"order":90},"Dependency Overrides","\u002Ffastapi\u002Ftesting\u002Fdependency-overrides",3,1782244113477]