How to Create Custom Test Fixtures in GenLayer Direct Mode: A Complete Guide

Define custom pytest fixtures in tests/direct/conftest.py by importing core fixtures like direct_vm, direct_deploy, and direct_alice, then compose them into reusable test utilities using @pytest.fixture decorators.

GenLayer's direct mode provides an in-memory virtual machine with the full GenVM stack, enabling fast unit-style tests without deploying to a live Studio environment. The test runner ships with essential fixtures that expose the virtual machine, deployment helpers, and pre-configured accounts. When you need reusable test logic—such as mock web responses, pre-deployed contract instances, or address conversion utilities—you create custom fixtures that compose these built-in primitives.

Understanding Direct Mode Fixtures

Direct mode testing relies on pytest fixtures provided by the genlayer-dev plugin. These core fixtures are automatically available in any test file under tests/direct/:

Fixture Purpose Key Methods/Attributes
direct_vm VirtualMachine instance running the GenVM stack mock_web(), mock_llm(), clear_mocks()
direct_deploy Helper to deploy contracts from file paths Returns deployed contract object callable
direct_alice, direct_bob, etc. Deterministic Address objects for test accounts .as_hex property for checksummed hex format

You never implement these fixtures yourself—they're registered automatically when the test suite runs.

Creating Custom Fixtures in conftest.py

Pytest discovers custom fixtures from tests/direct/conftest.py and makes them available to all direct-mode test files. The standard pattern involves importing core fixtures as arguments and returning composed objects.

Basic Fixture Structure


# tests/direct/conftest.py

import pytest

@pytest.fixture
def my_custom_fixture(direct_vm, direct_deploy):
    # Compose built-in fixtures into reusable test utility

    contract = direct_deploy("contracts/my_contract.py")
    direct_vm.mock_web(r".*example\.com.*", {"status": 200, "body": "{}"})
    return contract

Address Conversion Helper

The repository includes a practical utility for converting address bytes to checksummed hex format. In tests/direct/conftest.py, lines 4-15 define:


# tests/direct/conftest.py

def to_hex(addr_bytes):
    """Convert address bytes to checksummed hex matching contract output."""
    if hasattr(addr_bytes, "as_hex"):
        return addr_bytes.as_hex
    from genlayer.py.types import Address
    return Address(addr_bytes).as_hex

Convert this to an auto-injectable fixture:


# tests/direct/conftest.py

import pytest

@pytest.fixture
def hex_address(direct_alice):
    """Yield Alice's address as a checksummed hex string."""
    from genlayer.py.types import Address
    return Address(direct_alice).as_hex

Now tests simply declare hex_address as a parameter:

def test_player_points_default_zero(direct_deploy, hex_address):
    contract = direct_deploy("contracts/football_bets.py")
    assert contract.get_player_points(hex_address) == 0

Common Custom Fixture Patterns

Reusable Contract Instance

When multiple tests interact with the same contract, avoid repetitive deployment:

@pytest.fixture
def football_contract(direct_deploy):
    """Pre-deployed football bets contract for reuse across tests."""
    return direct_deploy("contracts/football_bets.py")

Mock Web and LLM Responses

Direct mode excels at testing contracts with intelligent contract calls. Create fixtures that pre-configure expected responses:

@pytest.fixture
def mock_successful_match(direct_vm):
    """
    Configure VM to return successful match data for BBC sports queries
    and structured extraction from LLM responses.
    """
    # Mock HTTP web query

    direct_vm.mock_web(
        r".*bbc\.com.*",
        {"status": 200, "body": "Match results: Team A 1 - 0 Team B"}
    )
    # Mock LLM extraction

    direct_vm.mock_llm(
        r".*Extract.*",
        '{"score": "1:0", "winner": 1}'
    )
    yield  # Provide control to test

    direct_vm.clear_mocks()  # Cleanup after test completes

Parameterized Test Data

Use params to run the same test against multiple scenarios:

@pytest.fixture(params=[
    {"home": "Arsenal", "away": "Chelsea", "expected_winner": 0},
    {"home": "Liverpool", "away": "Man City", "expected_winner": 1},
])
def bet_scenario(request):
    return request.param

Fixture Scoping for Performance

Control fixture lifecycle with the scope parameter:

Scope When Created Best For
"function" (default) Every test Isolated state, mocks that change per test
"module" Once per test file Expensive contract compilation
"session" Once for entire run Immutable configuration, shared resources

Example module-scoped fixture:

@pytest.fixture(scope="module")
def compiled_football_contract(direct_deploy):
    """Deploy once and reuse—safe if contract state is reset between tests."""
    return direct_deploy("contracts/football_bets.py")

Real-World Examples from the Repository

test_views.py: Basic Fixture Usage

The file tests/direct/test_views.py demonstrates fundamental patterns using direct_deploy, direct_vm, and direct_alice to test contract view functions without side effects.

test_create_bet.py and test_resolve_bet.py

tests/direct/test_create_bet.py and tests/direct/test_resolve_bet.py show advanced fixture composition:

  • Mocking web queries for real-time sports data
  • Mocking LLM responses for natural language extraction
  • Chaining multiple fixtures for complex bet resolution flows

Summary

  • Place custom fixtures in tests/direct/conftest.py for automatic discovery across the direct-mode test suite
  • Import core fixtures (direct_vm, direct_deploy, direct_alice, etc.) as function arguments to compose your own
  • Return values that tests need repeatedly—contract instances, formatted addresses, or pre-configured mock states
  • Use appropriate scoping (function, module, session) to balance isolation with performance
  • Clean up resources using yield fixtures when mocks or state need resetting

Frequently Asked Questions

What's the difference between direct mode and Studio deployment testing?

Direct mode runs contracts in-memory using the full GenVM stack without network calls, making tests execute in milliseconds. Studio deployment testing requires a running GenLayer node and validates actual consensus behavior. Use direct mode for rapid iteration on contract logic; use Studio testing for integration validation before production deployment.

Can I use custom fixtures from conftest.py in regular (non-direct) tests?

Fixtures in tests/direct/conftest.py are isolated to that directory. If you need fixtures for both direct and Studio tests, define them in a parent tests/conftest.py or import explicitly. The core direct_* fixtures are only meaningful in direct mode since they require the in-memory VM.

How do I debug a fixture that's not being discovered?

Verify the file is named exactly conftest.py (not conf_test.py), resides in tests/direct/, and contains valid Python with no syntax errors. Run pytest --fixtures tests/direct/ to list all available fixtures—your custom fixture should appear if discovery is working.

Why does my mock persist across tests when I expected cleanup?

Always use yield with a cleanup section, or call direct_vm.clear_mocks() explicitly. Alternatively, rely on scope="function" (the default) which recreates the fixture—and its mocks—fresh for each test. Module-scoped fixtures with mutable mock state require manual cleanup to prevent test pollution.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →