API Testing Architecture & Patterns
Treat API automation as three layers. Tests call the service layer; services orchestrate API client calls; validators own assertions and schema checks.
Layered model
Test Cases → Service Layer (business flows)
↓
API Client Layer (HTTP only)
↓
Response Validator (status, body, schema, headers)
| Layer | Responsibility | Robot home |
|---|---|---|
| API Client | URLs, methods, sessions, auth headers, raw GET/POST |
Custom library or resource wrapping RequestsLibrary |
| Service | Named flows: “register user”, “place order” | .robot or resource keywords composing client + validator |
| Validator | Status, JSON paths, schema, timing | Shared keywords / Python library |
API Client Layer
Encapsulate every HTTP call. Do not scatter POST On Session across test cases.
Example — Python library (ApiClientLibrary.py) wrapping RequestsLibrary-style behavior:
from robot.libraries.BuiltIn import BuiltIn
class ApiClientLibrary:
"""Thin wrapper: sessions and verbs only; no business assertions."""
def __init__(self) -> None:
self._builtin = BuiltIn()
self._requests = self._builtin.get_library_instance("RequestsLibrary")
def open_api_session(self, alias: str, url: str, headers: dict | None = None) -> None:
self._requests.create_session(alias, url, headers=headers or {})
def post_json(self, alias: str, path: str, payload: dict) -> object:
return self._requests.post_on_session(alias, path, json=payload)
In Robot, tests import Library ApiClientLibrary and call only Open Api Session, Post Json, etc.
Service Layer
Express business operations as keywords that combine calls and return handles or parsed data.
*** Settings ***
Resource ../clients/api_client.resource
Resource ../validators/http_validators.resource
*** Keywords ***
Create User And Expect Success
[Arguments] ${email} ${password}
${payload}= Build User Payload ${email} ${password}
${resp}= Post Json api /users ${payload}
Validate Created User Response ${resp}
${body}= Set Variable ${resp.json()}
RETURN ${body}
Response Validator
Centralize assertions. One keyword can enforce status, required JSON fields, and schema.
*** Keywords ***
Validate Created User Response
[Arguments] ${response}
Status Should Be 201 ${response}
Dictionary Should Contain Key ${response.json()} id
Validate Json Against Schema ${response.text} ${SCHEMA_USER}
RequestsLibrary basics
| Concern | Pattern | Notes |
|---|---|---|
| Session | Create Session, GET On Session, POST On Session, … |
Reuse cookies, default headers, base URL |
| Sessionless (RF-Requests 0.9.7+) | GET, POST, PUT, DELETE with full URL |
Good for smoke checks; less reuse |
| Status | expected_status= (on GET / GET On Session, etc.) or Status Should Be |
Avoid silent passes on 4xx/5xx |
*** Settings ***
Library RequestsLibrary
*** Test Cases ***
Example Session Flow
Create Session api https://api.example.com headers=${DEFAULT_HEADERS}
${r}= GET On Session api /health expected_status=200
Request design
| Technique | Purpose |
|---|---|
| Request builders | Keywords or Python helpers that merge base headers, trace IDs, and auth |
| Dynamic payloads | Timestamps, UUIDs, faker data — avoid fixed emails in suites |
| Shared token | Suite/setup keyword logs in once; stores token in a suite variable or session |
Rules (non-negotiable)
- NEVER assert raw JSON inline in test cases — use validator keywords or schema.
- ALWAYS go through the service layer for flows — not “call
/usersthen/profiles” in the test body. - TEST business flows (journey outcomes), not isolated endpoints as the primary artifact.
Following these rules keeps failures readable, reduces duplication, and lets you swap HTTP libraries behind the client layer without rewriting scenarios.