Skip to content

Test provider

Provider contract tests using Pact (XML).

This module demonstrates how to test a FastAPI provider (see provider.py) against a mock consumer using Pact. The mock consumer replays the requests defined by the consumer contract, and Pact validates that the provider responds as expected.

These tests show how provider verification ensures that the provider remains compatible with the consumer contract as the provider evolves. Provider state management is handled by manipulating the in-memory database directly before each interaction is replayed. For more, see the Pact Provider Test section of the Pact documentation.

Attributes

logger = logging.getLogger(__name__) module-attribute

Classes

Functions

app_server() -> str

Run the FastAPI server for provider verification.

RETURNS DESCRIPTION
str

The base URL of the running FastAPI server.

Source code in examples/http/xml_example/test_provider.py
@pytest.fixture(scope="session")
def app_server() -> str:
    """
    Run the FastAPI server for provider verification.

    Returns:
        The base URL of the running FastAPI server.
    """
    hostname = "localhost"
    port = pact._util.find_free_port()  # noqa: SLF001
    Thread(
        target=uvicorn.run,
        args=(app,),
        kwargs={"host": hostname, "port": port},
        daemon=True,
    ).start()
    for _ in range(50):
        with (
            contextlib.suppress(ConnectionRefusedError, OSError),
            socket.create_connection((hostname, port), timeout=0.1),
        ):
            break
        time.sleep(0.1)
    return f"http://{hostname}:{port}"

mock_user_does_not_exist(action: Literal['setup', 'teardown'], parameters: dict[str, Any]) -> None

Mock the provider state where a user does not exist.

This handler is invoked by Pact before and after each interaction that declares the state "the user doesn't exist". On setup, any existing user with the given id is removed from the in-memory database, ensuring the provider will return a 404. On teardown, nothing needs to be restored because nothing was added during setup, the user simply remains absent.

PARAMETER DESCRIPTION
action

Either "setup" (called before the interaction) or "teardown" (called after).

TYPE: Literal['setup', 'teardown']

parameters

State parameters from the consumer contract. Must contain "id".

TYPE: dict[str, Any]

Source code in examples/http/xml_example/test_provider.py
def mock_user_does_not_exist(
    action: Literal["setup", "teardown"],
    parameters: dict[str, Any],
) -> None:
    """
    Mock the provider state where a user does not exist.

    This handler is invoked by Pact before and after each interaction that
    declares the state `"the user doesn't exist"`. On setup, any existing user
    with the given `id` is removed from the in-memory database, ensuring the
    provider will return a 404. On teardown, nothing needs to be restored
    because nothing was added during setup, the user simply remains absent.

    Args:
        action:
            Either `"setup"` (called before the interaction) or `"teardown"`
            (called after).
        parameters:
            State parameters from the consumer contract. Must contain `"id"`.
    """
    logger.debug("mock_user_does_not_exist(%s, %r)", action, parameters)

    if "id" not in parameters:
        msg = "State must contain an 'id' field to mock user non-existence"
        raise ValueError(msg)

    uid = int(parameters["id"])

    if action == "setup":
        if user := UserDb.get(uid):
            UserDb.delete(user.id)
        return

    if action == "teardown":
        # Nothing was added during setup, so there is nothing to clean up.
        return

    msg = f"Unknown action: {action}"
    raise ValueError(msg)

mock_user_exists(action: Literal['setup', 'teardown'], parameters: dict[str, Any]) -> None

Mock the provider state where a user exists.

This handler is invoked by Pact before and after each interaction that declares the state "the user exists". On setup, a User record is inserted into the in-memory database using the id and name parameters from the consumer contract. On teardown, the record is removed so that subsequent interactions start from a clean slate.

PARAMETER DESCRIPTION
action

Either "setup" (called before the interaction) or "teardown" (called after).

TYPE: Literal['setup', 'teardown']

parameters

State parameters from the consumer contract. Must contain "id"; "name" is optional and defaults to "Alice".

TYPE: dict[str, Any]

Source code in examples/http/xml_example/test_provider.py
def mock_user_exists(
    action: Literal["setup", "teardown"],
    parameters: dict[str, Any],
) -> None:
    """
    Mock the provider state where a user exists.

    This handler is invoked by Pact before and after each interaction that
    declares the state `"the user exists"`. On setup, a `User` record is
    inserted into the in-memory database using the `id` and `name` parameters
    from the consumer contract. On teardown, the record is removed so that
    subsequent interactions start from a clean slate.

    Args:
        action:
            Either `"setup"` (called before the interaction) or `"teardown"`
            (called after).
        parameters:
            State parameters from the consumer contract. Must contain `"id"`;
            `"name"` is optional and defaults to `"Alice"`.
    """
    logger.debug("mock_user_exists(%s, %r)", action, parameters)
    user = User(
        id=int(parameters.get("id", 1)),
        name=str(parameters.get("name", "Alice")),
    )

    if action == "setup":
        UserDb.create(user)
        return

    if action == "teardown":
        with contextlib.suppress(KeyError):
            UserDb.delete(user.id)
        return

    msg = f"Unknown action: {action}"
    raise ValueError(msg)

test_provider(app_server: str, pacts_path: Path) -> None

Test the provider against the mock consumer contract.

This test runs the Pact verifier against the FastAPI provider, using the contract generated by the consumer tests.

Provider state handlers are essential in Pact contract testing. They allow the provider to be set up in a specific state before each interaction is verified. For example, if a consumer expects a user to exist for a certain request, the provider state handler ensures the in-memory database is populated accordingly. This enables repeatable and isolated contract verification: each interaction is tested in the correct context without relying on global or persistent state.

In this example, mock_user_exists and mock_user_does_not_exist are mapped to the state names declared in the consumer contract. They are responsible for setting up (and tearing down) the database so that the provider can respond correctly to each replayed request.

For additional information on state handlers, see Verifier.state_handler.

Source code in examples/http/xml_example/test_provider.py
def test_provider(app_server: str, pacts_path: Path) -> None:
    """
    Test the provider against the mock consumer contract.

    This test runs the Pact verifier against the FastAPI provider, using the
    contract generated by the consumer tests.

    Provider state handlers are essential in Pact contract testing. They allow
    the provider to be set up in a specific state before each interaction is
    verified. For example, if a consumer expects a user to exist for a certain
    request, the provider state handler ensures the in-memory database is
    populated accordingly. This enables repeatable and isolated contract
    verification: each interaction is tested in the correct context without
    relying on global or persistent state.

    In this example, `mock_user_exists` and `mock_user_does_not_exist` are
    mapped to the state names declared in the consumer contract. They are
    responsible for setting up (and tearing down) the database so that the
    provider can respond correctly to each replayed request.

    For additional information on state handlers, see
    [`Verifier.state_handler`][pact.verifier.Verifier.state_handler].
    """
    verifier = (
        Verifier("xml-provider")
        .add_source(pacts_path)
        .add_transport(url=app_server)
        .state_handler(
            {
                "the user exists": mock_user_exists,
                "the user doesn't exist": mock_user_does_not_exist,
            },
            teardown=True,
        )
    )

    verifier.verify()