A step definition binds a human-readable behavior-driven development (BDD) step to executable test code. A feature file states a scenario in Given/When/Then language; decorators such as @when and @then match its text, run a Python function, and let pytest evaluate assertions. section 2.4.4 section 4.3.1
Build a step definition by connecting four pieces:
- Write a scenario step that describes an observable action or expected result, such as parsing a header or receiving a status code. section 2.4.4 section 4.3.1
- Decorate a function with the matching step type, such as @when or @then, so pytest-bdd can locate it. section 4.3.1
- Use a parser pattern to capture changing values. A placeholder such as {type_5} turns the record-type text into a function parameter, allowing one definition to serve multiple scenarios. section 2.4.4
- Perform the action or assert the result inside the function. Fixtures can preserve shared state, such as an API response, across a scenario's steps. section 2.4.4 section 4.3.1
Checking an API status code
A feature can say, “When I make a GET request to the endpoint /files” and “Then the status code should be 200.” The when definition accepts the request type and endpoint, calls the test client, and stores the response in a fixture. The then definition parses the expected integer and compares it with the stored response status. The implementation currently supports GET only; other request types raise NotImplementedError until they are added. section 4.3.1
Listing 4.6 Step definitions for our BDD test
import pytest #1
from pytest_bdd import scenarios, when, then, parsers #1
from fastapi.testclient import TestClient #1
from app.main import app #1
client = TestClient(app)
scenarios("../features/files.feature") #2
@pytest.fixture #3
def api_response(): #3
return {} #3
@when(parsers.parse('I {http_request_type} the endpoint {endpoint}')) #4
def when_http_request_to_endpoint( #5
api_response, #5
http_request_type, #5
endpoint #5
): #5
if http_request_type == "GET": #5
response = client.get(endpoint) #5
else: #5
raise NotImplementedError #5
api_response["data"] = response #5
@then(parsers.parse("the status code should be {status_code:d}")) #6
def then_the_status_code_is(api_response, status_code): #7
assert ( #7
api_response["data"].status_code == status_code #7
), f"Expected {status_code}, but got
{when_http_request_to_endpoint.status_code}" #7
This listing shows a complete pair of API step definitions: one executes and stores a request, while the other parses and asserts the expected status code. section 4.3.1
Keep step definitions focused when scenarios grow. A dedicated step-definition file can isolate steps for one feature and allow that feature to be tested independently. For example, an endpoint scenario can prepare a clean database, post a file, request entries, and verify that every returned entry has the requested application value. section 8.8.3

