Cookbook

Real-world recipes that combine behave-data features.

Recipe: API test with fixtures, secrets, and diff

A complete example that sets up an API user from a fixture, loads an API token from an environment variable, and compares the response with an expected table.

Project layout

features/
  environment.py
  steps/
    api_steps.py
  api.feature

environment.py

from behave_data import (
    setup_data,
    before_feature_hook,
    before_scenario_hook,
    before_step_hook,
    after_scenario_hook,
    data_fixture,
)


@data_fixture("api_user")
def api_user():
    return {"name": "Alice", "role": "user"}


def before_all(context):
    setup_data(context)


def before_feature(context, feature):
    before_feature_hook(context, feature)


def before_scenario(context, scenario):
    before_scenario_hook(context, scenario)


def before_step(context, step):
    before_step_hook(context, step)


def after_scenario(context, scenario):
    after_scenario_hook(context, scenario)

api.feature

Feature: User API

  @needs_data:api_user
  Scenario: Get user profile
    Given the API token is "env:API_TOKEN"
    When I fetch the profile for user "api_user.name"
    Then the response matches
      | name:str | role:str |
      | Alice    | user     |

api_steps.py

import os
from behave import given, when, then
from behave_data import typed_wrap, diff


@given('the API token is "env:API_TOKEN"')
def step_token(context):
    context.token = context.data.resolve("env:API_TOKEN")


@when('I fetch the profile for user "api_user.name"')
def step_fetch(context):
    # In a real test you would call your API.
    # Here we simulate the response.
    context.response = {"name": "Alice", "role": "user"}


@then("the response matches")
def step_response_matches(context):
    diff(context.table, context.response)

Recipe: Data-driven Scenario Outline from CSV

features/data/users.csv:

name,email,age
alice,alice@example.com,30
bob,bob@example.com,25

features/users.feature:

Feature: User registration

  @load_examples:csv:features/data/users.csv
  Scenario Outline: Register user
    Given a user with name "<name>", email "<email>" and age <age>
    Then the user is valid

    Examples:
      | placeholder | placeholder | placeholder |

features/steps/user_steps.py:

from behave import given, then


@given('a user with name "{name}", email "{email}" and age {age}')
def step_user(context, name, email, age):
    context.user = {"name": name, "email": email, "age": int(age)}


@then("the user is valid")
def step_valid(context):
    assert context.user["age"] > 0
    assert "@" in context.user["email"]

Recipe: typed table with nullable dates

Feature: Events

  Scenario: List events
    Given events
      | name:str     | date:date?  |
      | Release      | 2024-07-15  |
      | Unscheduled  |             |
    Then 2 events exist
from behave import given, then
from behave_data import typed_wrap


@given("events")
def step_events(context):
    context.events = typed_wrap(context.table).typed_dicts()


@then("{count:int} events exist")
def step_count(context, count):
    assert len(context.events) == count
    assert context.events[1]["date"] is None

Recipe: reusable order builder

from behave_data import data_builder


@data_builder("order")
def order(overrides):
    items = overrides.get("items", [])
    total = sum(item["price"] for item in items)
    return {
        "items": items,
        "total": total,
        **overrides,
    }
order = context.data.build(
    "order",
    overrides={"items": [{"name": "Book", "price": 15}]},
)
# {"items": [{"name": "Book", "price": 15}], "total": 15}

Recipe: cleanup with @cleanup_after

Feature: Cleanup demo

  @cleanup_after
  Scenario: Create record
    Given I create a record with name "temp"
    Then the record exists
import os

records: list[str] = []


@given('I create a record with name "{name}"')
def step_create_record(context, name):
    records.append(name)

    def cleanup(_context):
        if name in records:
            records.remove(name)

    context._behave_data_cleanup_funcs.append(cleanup)


@then("the record exists")
def step_record_exists(context):
    assert records