How to Mock Web Requests in GenLayer Direct Tests
Use the direct_vm.mock_web(regex_pattern, response_dict) method to intercept HTTP calls made via gl.nondet.web and return controlled responses instead of hitting live endpoints.
GenLayer's direct testing mode executes contracts in-memory using the direct_vm fixture, eliminating the need for a live Studio deployment. This approach, implemented in the genlayerlabs/genlayer-project-boilerplate repository, allows you to simulate nondeterministic web interactions with deterministic mocks for faster, isolated test runs.
The mock_web API Signature
The direct_vm fixture provides the mock_web method to register URL patterns and their fabricated responses. According to the implementation in tests/direct/conftest.py, the method signature is:
direct_vm.mock_web(regex_pattern, response_dict)
Parameters:
regex_pattern— A Python regular expression string matched against the request URL.response_dict— A dictionary containing at least"status"(HTTP status code) and"body"(response payload as string or bytes).
When the contract code calls gl.nondet.web.get, post, or render, the VM checks its internal lookup table first. A matching regex short-circuits the actual network request and returns the supplied mock response.
Practical Mocking Patterns
Basic GET and POST Interception
To mock simple HTTP requests, register a pattern before invoking the contract method. This example from tests/direct/test_views.py intercepts sports data requests:
def test_simple_mock(direct_vm, direct_deploy):
contract = direct_deploy("contracts/football_bets.py")
direct_vm.mock_web(
r".*bbc\.com/sport/football/scores-fixtures.*",
{"status": 200, "body": "Match results available."},
)
contract.some_method_that_fetches()
Combining Web and LLM Mocks
Complex workflows often require mocking both external APIs and LLM parsing. The mock_web method works alongside mock_llm to isolate multi-step nondeterministic operations:
def test_resolve_bet(direct_vm, direct_deploy, direct_alice):
contract = direct_deploy("contracts/football_bets.py")
direct_vm.sender = direct_alice
# Mock the web request
direct_vm.mock_web(
r".*bbc\.com/sport/football/scores-fixtures.*",
{"status": 200, "body": "Match results available."},
)
# Mock the LLM parsing response
direct_vm.mock_llm(
r".*Extract the match result.*",
json.dumps({"score": "1:0", "winner": 1}),
)
contract.resolve_bet("2024-06-20_spain_italy")
Updating Mocks Mid-Test
When a test case invokes multiple web requests requiring different responses, use clear_mocks() to reset the internal lookup table before registering new patterns:
def test_multiple_resolves(direct_vm, direct_deploy, direct_alice):
contract = direct_deploy("contracts/football_bets.py")
direct_vm.sender = direct_alice
# First match
direct_vm.mock_web(r".*bbc\.com.*", {"status": 200, "body": "data1"})
contract.resolve_bet("match1")
# Reset and reconfigure for second match
direct_vm.clear_mocks()
direct_vm.mock_web(r".*bbc\.com.*", {"status": 200, "body": "data2"})
contract.resolve_bet("match2")
Mocking Screenshot Rendering
For contracts using gl.nondet.web.render in screenshot mode, provide a bytes payload (typically empty) in the response_dict:
def test_mock_web_intercepts_render_screenshot(direct_vm, direct_deploy, visual_contract_path):
direct_vm.mock_web(r".*", {"status": 200, "body": b""})
contract = direct_deploy(visual_contract_path)
contract.render_screenshot() # Calls gl.nondet.web.render(..., mode='screenshot')
This pattern is demonstrated in tests/integration/test_new_features.py.
How the Interception Works
Under the hood, mock_web registers patterns in a lookup table inside the DirectVM instance. When contract code executes gl.nondet.web.get(url, ...) or similar methods, the VM checks this table first. A matching regex short-circuits the actual network request and returns the supplied mock response.
This mechanism applies identically to render calls for both text/html and screenshot modes. For textual renders, the "body" field contains the HTML or text content; for screenshot mocks, the framework accepts a byte string (commonly empty b"").
Summary
- Use
direct_vm.mock_web(regex, {"status": int, "body": str|bytes})to intercept URLs matching the pattern. - Register mocks before contract invocation; subsequent matching calls return the fabricated response.
- Call
direct_vm.clear_mocks()to reset state between test scenarios. - Supply bytes payloads (e.g.,
b"") when mocking screenshot renders. - Reference implementations exist in
tests/direct/test_views.pyandtests/integration/test_new_features.py.
Frequently Asked Questions
Can I mock multiple different endpoints in the same test?
Yes. Call mock_web multiple times with different regex patterns before executing the contract method. Each pattern is stored in the VM's lookup table and matched independently against outgoing requests made via gl.nondet.web.
What happens if a web request doesn't match any mock pattern?
If no registered regex matches the request URL, the VM will attempt to execute the actual network call or raise an error depending on the direct test configuration. Always ensure your patterns cover all URLs the contract might request during the test to maintain deterministic behavior.
Does mock_web work with POST requests and custom headers?
Yes. The mock_web method intercepts any gl.nondet.web call including post and render. While the response dictionary primarily defines status and body, the interception occurs at the VM level regardless of the HTTP method or headers used in the contract code.
How do I mock paginated or sequential API calls?
Use clear_mocks() between contract invocations to update the response for subsequent calls. Alternatively, register multiple distinct patterns if the URLs differ, such as by including page numbers or query parameters in the regex.
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 →