From 1b260989ce5267544370aee07c055a0a59c7daa6 Mon Sep 17 00:00:00 2001
From: Ridwan Nurudeen <47521064+Ridwannurudeen@users.noreply.github.com>
Date: Mon, 2 Mar 2026 09:05:33 +0100
Subject: [PATCH 1/3] docs: add testing guide for intelligent contracts
---
.../intelligent-contracts/testing.mdx | 215 ++++++++++++++++++
1 file changed, 215 insertions(+)
create mode 100644 pages/developers/intelligent-contracts/testing.mdx
diff --git a/pages/developers/intelligent-contracts/testing.mdx b/pages/developers/intelligent-contracts/testing.mdx
new file mode 100644
index 00000000..311aa256
--- /dev/null
+++ b/pages/developers/intelligent-contracts/testing.mdx
@@ -0,0 +1,215 @@
+import { Callout } from 'nextra-theme-docs'
+
+# Testing Intelligent Contracts
+
+The [GenLayer Testing Suite](https://pypi.org/project/genlayer-test/) (`genlayer-test`) is a pytest-based framework for testing Intelligent Contracts. It provides two execution modes to match your workflow.
+
+## Installation
+
+```bash
+pip install genlayer-test
+```
+
+## Two Modes at a Glance
+
+| | Direct Mode | Studio Mode |
+|-------------------|-------------------------------------------|--------------------------------------------------|
+| **How it works** | Runs contract code in-memory (no network) | Deploys to GenLayer Studio via RPC |
+| **Speed** | Milliseconds per test | Minutes per test |
+| **Prerequisites** | Python 3.12+ | Python 3.12+ and GenLayer Studio (Docker) |
+| **Best for** | Unit tests, rapid iteration, CI/CD | Integration tests, consensus validation, testnet |
+| **Mocking** | `mock_web` / `mock_llm` cheatcodes | Mock validators with LLM/web responses |
+
+
+ **Start with Direct Mode.** It runs in milliseconds, requires no Docker, and covers the vast majority of contract logic. Add Studio Mode tests only when you need multi-validator consensus or full-network behavior.
+
+
+## Direct Mode
+
+Direct Mode runs your contract Python code in-process -- no simulator, no Docker required.
+
+### Quick Start
+
+```python
+# tests/test_storage.py
+
+def test_storage(direct_vm, direct_deploy):
+ # Deploy the contract in-memory
+ storage = direct_deploy("contracts/Storage.py", "initial value")
+
+ # Call view methods directly
+ assert storage.get_storage() == "initial value"
+
+ # Call write methods directly
+ storage.update_storage("updated")
+ assert storage.get_storage() == "updated"
+```
+
+Run with pytest:
+
+```bash
+pytest tests/ -v
+```
+
+### Fixtures
+
+Direct Mode provides built-in pytest fixtures:
+
+| Fixture | Description |
+|---------|-------------|
+| `direct_vm` | VM context with cheatcodes |
+| `direct_deploy` | Deploy a contract in-memory |
+| `direct_alice`, `direct_bob`, `direct_charlie` | Predefined test addresses |
+| `direct_owner` | Default sender address |
+| `direct_accounts` | List of 10 test addresses |
+
+### Cheatcodes
+
+The `direct_vm` fixture exposes cheatcodes for controlling test execution:
+
+#### Changing the Sender
+
+```python
+def test_access_control(direct_vm, direct_deploy, direct_alice, direct_bob):
+ contract = direct_deploy("contracts/MyContract.py")
+
+ # Set sender permanently
+ direct_vm.sender = direct_alice
+ contract.owner_action() # Called as alice
+
+ # Prank: temporarily change sender for a single call
+ with direct_vm.prank(direct_bob):
+ with direct_vm.expect_revert("Unauthorized"):
+ contract.owner_action() # Reverts -- bob is not owner
+```
+
+#### Snapshots and Revert
+
+```python
+def test_state_isolation(direct_vm, direct_deploy):
+ contract = direct_deploy("contracts/Counter.py")
+
+ snap_id = direct_vm.snapshot()
+ contract.increment()
+ assert contract.get_count() == 1
+
+ direct_vm.revert(snap_id)
+ assert contract.get_count() == 0 # State fully restored
+```
+
+Snapshots capture full state: storage, mocks, sender, and validators.
+
+#### Expecting Reverts
+
+```python
+def test_insufficient_balance(direct_vm, direct_deploy, direct_alice):
+ contract = direct_deploy("contracts/Token.py", direct_alice, 100)
+
+ with direct_vm.expect_revert("Insufficient balance"):
+ contract.transfer(direct_alice, 999)
+```
+
+### Mocking Web and LLM Calls
+
+Non-deterministic calls (`gl.nondet.web`, `gl.nondet.exec_prompt`) must be mocked in Direct Mode. Use regex patterns to match URLs and prompt text.
+
+```python
+def test_price_feed(direct_vm, direct_deploy):
+ # Mock a web response (regex pattern matches the URL)
+ direct_vm.mock_web(
+ r"api\.example\.com/price",
+ {"status": 200, "body": '{"price": 42.50}'}
+ )
+
+ contract = direct_deploy("contracts/PriceFeed.py")
+ contract.update_price()
+ assert contract.get_price() == 4250 # Stored as integer
+```
+
+```python
+def test_sentiment_analysis(direct_vm, direct_deploy):
+ # Mock an LLM response (regex matches the prompt text)
+ direct_vm.mock_llm(r"classify.*sentiment", "positive")
+
+ contract = direct_deploy("contracts/Sentiment.py")
+ contract.analyze("I love GenLayer!")
+ assert contract.get_sentiment() == "positive"
+```
+
+
+ Set `direct_vm.strict_mocks = True` to raise an error if any registered mock is never matched. This catches stale or misspelled patterns before they hide bugs.
+
+
+### Testing Validator Consensus
+
+Verify that your equivalence principle produces consistent results across validators:
+
+```python
+def test_consensus_agreement(direct_vm, direct_deploy):
+ direct_vm.mock_llm(r".*", '{"verdict": "true"}')
+
+ contract = direct_deploy("contracts/FactChecker.py")
+ # Run as the leader -- captures the validator function internally
+ contract.check_claim("The sky is blue")
+
+ # Swap mocks to simulate a dissenting validator
+ direct_vm.clear_mocks()
+ direct_vm.mock_llm(r".*", '{"verdict": "false"}')
+ assert direct_vm.run_validator() is False # Validator disagrees -> undetermined
+```
+
+## Studio Mode
+
+Studio Mode deploys your contracts to a running GenLayer Studio instance and interacts via RPC. Use it when you need:
+
+- Multi-validator consensus with real network behavior
+- Verification on `localnet` or `studionet`
+- Pre-testnet integration checks
+
+### Prerequisites
+
+- GenLayer Studio running locally (`genlayer up`)
+- Python 3.12+
+
+### Quick Start
+
+```python
+from gltest import get_contract_factory, get_default_account
+from gltest.assertions import tx_execution_succeeded
+
+def test_contract_integration(default_account):
+ factory = get_contract_factory("Storage")
+ contract = factory.deploy(args=["initial"])
+
+ tx = contract.update_storage(args=["new value"]).transact()
+ assert tx_execution_succeeded(tx)
+
+ result = contract.get_storage().call()
+ assert result == "new value"
+```
+
+Run with the `gltest` CLI:
+
+```bash
+gltest tests/ -v
+gltest --network studionet
+gltest --leader-only # Skip consensus validation (faster)
+```
+
+For the full Studio Mode API -- mock validators, LLM/web responses, multi-network configuration -- see the [genlayer-test API Reference](/api-references/genlayer-test).
+
+## Testing Strategy
+
+Structure your test suite in layers:
+
+1. **Pure storage tests first** -- verify `__init__`, view methods, and write methods that do not call `gl.nondet`. These run instantly and catch most logic bugs.
+
+2. **Mock non-deterministic calls** -- add `mock_web` / `mock_llm` to test the full execution flow with controlled outputs. Cover both happy paths and edge cases (empty responses, unexpected LLM output, HTTP errors).
+
+3. **Consensus tests** -- use `direct_vm.run_validator()` to confirm your equivalence principle produces agreement on typical inputs. Also verify that validators disagree on inputs designed to be ambiguous.
+
+4. **Studio Mode last** -- run a smaller set of integration tests against `localnet` in CI to verify end-to-end behavior with real validators.
+
+
+ Enable `direct_vm.check_pickling = True` to catch serialization bugs early. GenLayer stores contract state by pickling Python objects -- any custom class not decorated with `@allow_storage` and `@dataclass` will fail at runtime.
+
From 757cccf1308a478c310c98208a0940090408cd7e Mon Sep 17 00:00:00 2001
From: Ridwan Nurudeen <47521064+Ridwannurudeen@users.noreply.github.com>
Date: Mon, 2 Mar 2026 09:11:21 +0100
Subject: [PATCH 2/3] docs: add testing to intelligent contracts navigation
---
pages/developers/intelligent-contracts/_meta.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/pages/developers/intelligent-contracts/_meta.json b/pages/developers/intelligent-contracts/_meta.json
index d996e6d3..6a90d4ce 100644
--- a/pages/developers/intelligent-contracts/_meta.json
+++ b/pages/developers/intelligent-contracts/_meta.json
@@ -8,6 +8,7 @@
"first-intelligent-contract": "Your First Intelligent Contract",
"equivalence-principle": "Equivalence Principle",
"debugging": "Debugging",
+ "testing": "Testing",
"deploying": "Deploying",
"crafting-prompts": "Crafting Prompts",
"security-and-best-practices": "Security and Best Practices",
From 73434127b624619bae192314c1bcc552842e5e59 Mon Sep 17 00:00:00 2001
From: Ridwan Nurudeen <47521064+Ridwannurudeen@users.noreply.github.com>
Date: Mon, 2 Mar 2026 09:22:33 +0100
Subject: [PATCH 3/3] docs: address CodeRabbit review comments on testing guide
---
pages/developers/intelligent-contracts/testing.mdx | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/pages/developers/intelligent-contracts/testing.mdx b/pages/developers/intelligent-contracts/testing.mdx
index 311aa256..ac594ef0 100644
--- a/pages/developers/intelligent-contracts/testing.mdx
+++ b/pages/developers/intelligent-contracts/testing.mdx
@@ -33,7 +33,7 @@ Direct Mode runs your contract Python code in-process -- no simulator, no Docker
```python
# tests/test_storage.py
-def test_storage(direct_vm, direct_deploy):
+def test_storage(direct_deploy):
# Deploy the contract in-memory
storage = direct_deploy("contracts/Storage.py", "initial value")
@@ -136,7 +136,7 @@ def test_sentiment_analysis(direct_vm, direct_deploy):
assert contract.get_sentiment() == "positive"
```
-
+
Set `direct_vm.strict_mocks = True` to raise an error if any registered mock is never matched. This catches stale or misspelled patterns before they hide bugs.
@@ -174,9 +174,10 @@ Studio Mode deploys your contracts to a running GenLayer Studio instance and int
### Quick Start
```python
-from gltest import get_contract_factory, get_default_account
+from gltest import get_contract_factory
from gltest.assertions import tx_execution_succeeded
+# `default_account` is a pre-provided pytest fixture supplied by genlayer-test for Studio Mode
def test_contract_integration(default_account):
factory = get_contract_factory("Storage")
contract = factory.deploy(args=["initial"])