API Testing (surety-api)¶
The surety-api extension provides API contracts, HTTP interaction,
contract-based mocking, and request verification.
pip install surety-api
Defining an API Contract¶
An API contract binds request and response schemas to an HTTP endpoint:
from surety.api import ApiContract, HttpMethod
from surety import Dictionary, String, Int
# Schemas — define data structure
class CreateOrderRequest(Dictionary):
ProductId = Int(name='product_id')
Quantity = Int(name='quantity', min_val=1, max_val=100)
class OrderResponse(Dictionary):
OrderId = Int(name='order_id')
Status = String(name='status', default='pending')
Total = String(name='total')
# Contract — binds schemas to an API endpoint
class CreateOrder(ApiContract):
method = HttpMethod.POST
url = '/api/v2/orders'
req_body = CreateOrderRequest
resp_body = OrderResponse
The schemas (CreateOrderRequest, OrderResponse) define what the data
looks like. The contract (CreateOrder) defines where and how the data is
exchanged.
Available HTTP methods: POST, GET, HEAD, PATCH, DELETE,
PUT, TRACE, OPTIONS.
Path Parameters¶
Use curly braces in the URL and pass path_params:
class GetOrder(ApiContract):
method = HttpMethod.GET
url = '/api/v2/orders/{order_id}'
resp_body = OrderResponse
GetOrder().call(path_params={'order_id': 42})
Making API Calls¶
Use call() to execute HTTP requests:
CreateOrder().call(
headers={'Authorization': 'Bearer tk_test_abc123'}
)
ApiCaller¶
ApiCaller provides method chaining with built-in verification:
from surety.api import ApiCaller
class CreateOrderCaller(ApiCaller):
contract = CreateOrder
CreateOrderCaller(
req_body={'product_id': 501, 'quantity': 3},
headers={'Authorization': 'Bearer tk_test_abc123'}
).request().verify_response(resp_body=OrderResponse().value)
Response verification supports comparison rules and normalization:
from surety.diff.rules import has_some_value
caller.verify_response(
resp_body=OrderResponse().value,
rules={'order_id': has_some_value},
normalize=True
)
Mocking with MockServer¶
Set up mock responses for downstream services:
from surety.api import MockServer
mock = MockServer()
mock.reply(
method='POST',
url='/external-api/v1/validate',
body={'valid': True, 'score': 0.98},
status=200
)
# After the test runs, verify the mock was called
mock.verify_all_mocks_called()
Additional MockServer methods:
# Capture requests to a URL
captured = mock.catch(url='/external-api/v1/validate', timeout=5)
# Check for unexpected unmocked requests
unaddressed = mock.get_unaddressed_requests()
# Wait for all mocks to be called
mock.wait_for_mocks_to_be_called(timeout_seconds=3)
# Reset all expectations
mock.reset()
Contract-based Mocking¶
Use reply() directly on an API contract to mock its endpoint using the
contract’s schemas:
contract = CreateOrder()
contract.reply(
body=CreateOrderRequest(),
status=201
)
Verifying API Calls¶
Assert that a mocked endpoint received the expected request:
contract = CreateOrder()
contract.reply(status=201)
# ... test logic that triggers the API call ...
contract.verify_called(
expected=OrderResponse()
)
Verification supports comparison rules:
from surety.diff.rules import has_some_value
contract.verify_called(
expected=OrderResponse(),
rules={OrderResponse.OrderId.name: has_some_value}
)