How to Write Direct Mode Tests with the mock_web Fixture in the GenLayer Boilerplate
Direct-mode tests run contracts in-memory using the direct_vm harness, and the mock_web fixture lets you intercept external HTTP calls to return deterministic responses instead of performing real network requests.
The genlayer-project-boilerplate repository ships with a lightweight test VM that executes GenLayer contracts locally. Writing direct mode tests with the mock_web fixture lets you validate contract logic that relies on gl.nondet.web without connecting to a live GenLayer Studio instance or the open internet.
What Is Direct Mode Testing?
Direct mode is an in-memory execution environment for GenLayer contracts. The direct_vm object, provided by the boilerplate's test harness, deploys contracts locally and exposes helper methods to simulate callers, mock web traffic, and stub LLM prompts. This approach keeps tests fast and deterministic by removing external dependencies.
According to the genlayer-project-boilerplate source code, the direct-mode test suite lives under tests/direct/ and is initialized through tests/direct/__init__.py. The conftest.py file in that directory supplies shared helpers such as to_hex for address conversion.
How the mock_web Fixture Works
The mock_web fixture is a method on the direct_vm instance. It registers a regular-expression pattern against a mocked HTTP response dictionary. When the contract invokes gl.nondet.web.get, gl.nondet.web.post, or gl.nondet.web.render, the VM compares the requested URL against registered patterns and returns the matching status and body instead of executing a real HTTP request.
This interception happens entirely inside the test harness. In contracts/football_bets.py, the _check_match method constructs a URL from the match date and calls gl.nondet.web.render to retrieve the score page before passing the result to an LLM prompt. The fixture is used extensively in tests/direct/test_views.py and tests/direct/test_resolve_bet.py, where it replaces the network layer for contracts that scrape web data before processing it with an LLM.
Writing a Direct Mode Test with mock_web
A complete test that leverages mock_web follows a six-step workflow. The examples below reference the football_bets.py contract, which fetches match results via gl.nondet.web.render and parses them with gl.nondet.exec_prompt.
Deploy the Contract and Set the Caller
Use direct_deploy to load the contract file into memory, then assign direct_vm.sender so that gl.message.sender_address resolves to the correct account inside the contract.
contract = direct_deploy("contracts/football_bets.py")
direct_vm.sender = direct_alice
alice = to_hex(direct_alice)
Register URL Mocks with mock_web
Call direct_vm.mock_web(pattern, response) with a regex pattern that matches the URL your contract will request. The response must be a dictionary containing status and body.
direct_vm.mock_web(
r".*bbc\.com/sport/football/scores-fixtures.*",
{"status": 200, "body": "Match result: 1:0. Winner: team 1."},
)
Mock LLM Responses for Nondeterministic Pipelines
If the contract passes the web response to gl.nondet.exec_prompt, register a matching mock_llm pattern so the entire nondeterministic chain stays predictable.
direct_vm.mock_llm(
r".*Extract the match result.*",
json.dumps({"score": "1:0", "winner": 1}),
)
Invoke the Contract Method
Trigger the contract function that causes the external request. Because the URL matches the mocked pattern, the VM returns the stubbed response.
contract.resolve_bet("2024-06-20_spain_italy")
Assert State Changes
Verify that the contract updated its internal state based on the mocked data.
assert contract.get_player_points(alice) == 1
bet = contract.get_bets()[alice]["2024-06-20_spain_italy"]
assert bet.has_resolved is True
assert bet.real_winner == "1"
assert bet.real_score == "1:0"
Complete Test Example
Here is a full test from the boilerplate that chains mock_web and mock_llm to resolve a winning bet.
def test_resolve_winning_bet(direct_vm, direct_deploy, direct_alice):
# 1️⃣ Deploy contract
contract = direct_deploy("contracts/football_bets.py")
# 2️⃣ Simulate caller
direct_vm.sender = direct_alice
alice = to_hex(direct_alice)
# 3️⃣ Create a bet that will be resolved later
contract.create_bet("2024-06-20", "Spain", "Italy", "1")
# 4️⃣ Register web and LLM mocks for the match result
direct_vm.mock_web(
r".*bbc\.com/sport/football/scores-fixtures.*",
{"status": 200, "body": "Match result: 1:0. Winner: team 1."},
)
direct_vm.mock_llm(
r".*Extract the match result.*",
json.dumps({"score": "1:0", "winner": 1}),
)
# 5️⃣ Resolve the bet – the contract will hit the mocked web endpoint
contract.resolve_bet("2024-06-20_spain_italy")
# 6️⃣ Verify state
assert contract.get_player_points(alice) == 1
bet = contract.get_bets()[alice]["2024-06-20_spain_italy"]
assert bet.has_resolved is True
assert bet.real_winner == "1"
assert bet.real_score == "1:0"
Handling Multiple Mock Phases in One Test
When a single test function needs different responses for successive calls, invoke direct_vm.clear_mocks() between phases. This removes all registered mock_web and mock_llm stubs so you can register new ones without interference.
def test_multiple_mock_phases(direct_vm, direct_deploy, direct_alice):
contract = direct_deploy("contracts/football_bets.py")
direct_vm.sender = direct_alice
alice = to_hex(direct_alice)
# First match – win
contract.create_bet("2024-06-20", "Spain", "Italy", "1")
direct_vm.mock_web(r".*bbc.*", {"status": 200, "body": "ok"})
direct_vm.mock_llm(r".*Extract.*", json.dumps({"score": "2:0", "winner": 1}))
contract.resolve_bet("2024-06-20_spain_italy")
assert contract.get_player_points(alice) == 1
# Clear previous mocks to change the outcome
direct_vm.clear_mocks()
# Second match – loss
contract.create_bet("2024-06-21", "France", "Germany", "2")
direct_vm.mock_web(r".*bbc.*", {"status": 200, "body": "ok"})
direct_vm.mock_llm(r".*Extract.*", json.dumps({"score": "0:1", "winner": 2}))
contract.resolve_bet("2024-06-21_france_germany")
assert contract.get_player_points(alice) == 1 # no additional point
Key Files in the Boilerplate
The following files implement and demonstrate direct mode tests with mock_web:
tests/direct/__init__.py– Initializes the direct-mode test environment.tests/direct/conftest.py– Provides helper utilities such asto_hexfor address conversion.tests/direct/test_views.py– Contains basic view-method tests and introductorymock_webusage.tests/direct/test_resolve_bet.py– Shows a fullmock_webplusmock_llmworkflow for bet resolution.contracts/football_bets.py– The target contract that callsgl.nondet.web.renderandgl.nondet.exec_prompt.
Summary
- Direct mode executes contracts in-memory via the
direct_vmharness, eliminating the need for a live GenLayer Studio instance. - The
mock_webfixture registers regex patterns that interceptgl.nondet.web.get,gl.nondet.web.post, andgl.nondet.web.rendercalls. - Pair
mock_webwithmock_llmwhenever the contract feeds web data intogl.nondet.exec_promptto keep the entire pipeline deterministic. - Use
direct_vm.senderto set the caller address sogl.message.sender_addressresolves correctly. - Call
direct_vm.clear_mocks()between test phases when you need to swap responses within the same function. - Reference
tests/direct/test_resolve_bet.pyandtests/direct/test_views.pyfor production-ready examples in the boilerplate.
Frequently Asked Questions
What HTTP methods does mock_web intercept?
The mock_web fixture intercepts calls made through gl.nondet.web.get, gl.nondet.web.post, and gl.nondet.web.render. When the contract code invokes any of these methods, the test VM matches the requested URL against registered regex patterns and returns the mocked status and body dictionary instead of performing a real network request.
Do I need to mock the LLM separately from the web request?
Yes. If the contract logic passes the web response into gl.nondet.exec_prompt, you must also call direct_vm.mock_llm with a pattern that matches the prompt. This ensures the entire nondeterministic pipeline—from web fetch to structured extraction—returns predictable data under direct mode.
How do I test multiple scenarios with different responses in one test?
Call direct_vm.clear_mocks() after the first scenario. This wipes all existing mock_web and mock_llm registrations, allowing you to define new patterns and responses for subsequent contract invocations without stale stubs interfering.
What format should the mock_web response parameter use?
The response must be a Python dictionary with at least status and body keys, mimicking an HTTP response. For example: {"status": 200, "body": "Match result: 1:0"}. The VM returns exactly these values to the contract when the URL pattern matches.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →