POST and error contracts
Chapter lab: API foundations
This lesson continues in the same lab as the lesson before it. If you don’t have it yet, download it here.
Download ch11-api-foundations.zip
Or fetch and unpack it from a terminal in VS Code:
curl -fsSLO https://manykarim.github.io/robotframework-essentials-web/labs/ch11-api-foundations.zip && unzip -q ch11-api-foundations.zip && rm ch11-api-foundations.zipInvoke-WebRequest https://manykarim.github.io/robotframework-essentials-web/labs/ch11-api-foundations.zip -OutFile ch11-api-foundations.zip; Expand-Archive ch11-api-foundations.zip -DestinationPath .; Remove-Item ch11-api-foundations.zipOpen the folder in VS Code with code rfe-ch11-api-foundations (or File → Open Folder…) and accept the recommended RobotCode and Python extensions. Then set it up:
uv syncpython3 -m venv .venvsource .venv/bin/activatepip install -r requirements.txtpython -m venv .venv.venv\Scripts\Activate.ps1pip install -r requirements.txtKeep the environment active in every terminal where you run lesson commands.
poetry install --no-rootPython 3.13 or newer is required; uv installs it when it is missing.
Also needed for this lab
Start the RF Demo Shop in its own terminal and keep it running while you work:
uv run python demo_app/serve.pypython demo_app/serve.pypoetry run python demo_app/serve.pyOpen in browserNo setup? Try the lab right here: its files, an editor and a terminal, running in your browser.
What's inside
Directoryrfe-ch11-api-foundations/
Directory.vscode/
- extensions.json
Directorychecks/
- smoke.robot
Directorydemo_app/
- api.py
- serve.py
- store.py
- web.py
Directoryresources/
- api.resource
- .gitignore
- .python-version
- LAB.md
- pyproject.toml
- README.md
- requirements.txt
- uv.lock
A contract has two sides: the success case and the failure case. POST creates
a resource — send a dictionary as JSON with json=${body}. And a good API test
also asserts the error contract: a missing product should return 404, not
a surprise.
Use expected_status=404 so the request keyword treats the 404 as expected
instead of failing.
Task 1 — POST an order and assert a 404 for a missing product
Section titled “Task 1 — POST an order and assert a 404 for a missing product”Goal: create checks/post.robot with two tests — one that POSTs an order and
asserts a 201, and one that GETs a missing product and asserts a 404.
Here is the same success-and-failure pair over different data. Adapt it.
Worked example — adapt it, don’t copy it
*** Settings ***Library RequestsLibraryResource ../resources/api.resource
*** Test Cases ***Ordering A Backpack Returns 201 ${body}= Create Dictionary product=trail-backpack quantity=1 ${resp}= POST ${API_URL}/api/orders json=${body} Status Should Be 201 ${resp} Should Be Equal ${resp.json()}[status] complete
Missing Product Is A 404 ${resp}= GET ${API_URL}/api/products/ghost expected_status=404 Should Be Equal ${resp.json()}[error] product not foundDone when
both tests are defined.
Hint 1
Build the order body with Create Dictionary product=travel-mug quantity=2,
then POST ${API_URL}/api/orders json=${body} and assert 201.
Hint 2
For the error test, pass expected_status=404 on the GET so the keyword accepts
it, then assert Should Be Equal ${resp.json()}[error] product not found.
Solution
*** Settings ***Library RequestsLibraryResource ../resources/api.resource
*** Test Cases ***Placing An Order Returns 201 ${body}= Create Dictionary product=travel-mug quantity=2 ${resp}= POST ${API_URL}/api/orders json=${body} Status Should Be 201 ${resp} Should Be Equal ${resp.json()}[status] complete
Unknown Product Is A 404 ${resp}= GET ${API_URL}/api/products/nope expected_status=404 Should Be Equal ${resp.json()}[error] product not foundTask 2 — Run both green
Section titled “Task 2 — Run both green”Run:
uv run robot checksrobot checkspoetry run robot checksDone when
the order posts with 201, and the missing product returns 404.
Check your understanding
Quick recall. Your first answer stays in this browser; the quiz never blocks your progress.
How do you send a JSON body with POST?
POST ${API_URL}/api/orders json=${body}sends a dictionary as a JSON body.Show the answer
Answer:
POST url json=${dict}—POST ${API_URL}/api/orders json=${body}sends a dictionary as a JSON body.To assert a 404 without failing the request, use…
expected_status=404tells RequestsLibrary the 404 is expected, so the keyword doesn't fail.Show the answer
Answer:
GET url expected_status=404—expected_status=404tells RequestsLibrary the 404 is expected, so the keyword doesn't fail.
Key terms
- POSTkeyword
POST url json=${dict}— create a resource.- expected_statusconcept
- Assert a non-2xx status is expected, e.g.
GET url expected_status=404.