A pytest-native framework for testing NEAR smart contracts in Python.
✨ Features • 📥 Installation • 🚀 Getting Started • 📊 Examples • 📘 API Reference
near-pytest
enables intuitive testing of NEAR smart contracts directly from Python. It provides a pytest-native approach that automatically handles compilation, sandbox initialization, account creation, contract deployment, and state management - allowing you to focus on writing tests that truly validate your contract's behavior.
🚀 Zero-config setup - Everything works out-of-the-box: automatic sandbox management, contract compilation, and account creation
⚡ Lightning-fast tests - State snapshots between tests eliminate repeated setup operations, making test suites run orders of magnitude faster than traditional approaches
🧩 Intuitive API - Simple, Pythonic interfaces for interacting with contracts and accounts
🤝 Two testing styles - Choose between class-based tests or modular fixtures-based tests according to your preference
🔄 State snapshots - Create a full blockchain state once, then reset to this state between tests in milliseconds instead of seconds
🛠️ Complete toolchain integration - Seamless integration with NEAR compiler, Python SDK, and sandbox
🧪 Pytest native - Leverages the full power of pytest for smart contract testing
🔍 Rich logging - Detailed logs for troubleshooting and debugging
🧠 Smart caching - Automatically caches compiled contracts for faster subsequent runs
✨ Requests-like Response API - Familiar interface for handling contract responses with .json()
method and .text
property
uv add near-pytest
- Python 3.11 or higher
- For contract compilation:
nearc
package - The framework automatically handles downloading and installing the NEAR sandbox binary
You can choose between two testing approaches:
- Modular Fixtures Approach - More pytest-native, with composable fixtures
- Class-based Approach - Traditional approach with test classes inheriting from
NearTestCase
import pytest
from near_pytest.compiler import compile_contract
@pytest.fixture(scope="session")
def counter_wasm():
"""Compile the counter contract."""
return compile_contract("path/to/contract.py", single_file=True)
@pytest.fixture
def counter_contract(sandbox, counter_wasm):
"""Deploy a fresh counter contract for each test."""
account = sandbox.create_random_account("counter")
return sandbox.deploy(
wasm_path=counter_wasm,
account=account,
init_args={"starting_count": 0}
)
def test_increment(counter_contract, localnet_alice_account):
"""Test incrementing the counter."""
# Call contract method using method chaining
result = counter_contract.call("increment").as_transaction(localnet_alice_account)
assert int(result) == 1
# View state
count = counter_contract.call("get_count").as_view()
assert int(count) == 1
from near_pytest.testing import NearTestCase
class TestCounter(NearTestCase):
@classmethod
def setup_class(cls):
# Call parent setup method first
super().setup_class()
# Compile the contract
wasm_path = cls.compile_contract("path/to/contract.py", single_file=True)
# Create account for contract
cls.counter = cls.create_account("counter")
# Deploy contract
cls.counter_contract = cls.deploy_contract(
cls.counter,
wasm_path,
init_args={"starting_count": 0}
)
# Create test accounts
cls.alice = cls.create_account("alice")
cls.bob = cls.create_account("bob")
# Save initial state for future resets
cls.save_state()
def setup_method(self):
# Reset to initial state before each test method
self.reset_state()
def test_increment(self):
# Call contract method
result = self.counter_contract.call("increment", {})
assert int(result.text) == 1
from near_pytest.modular import sandbox, compile_contract, sandbox_alice
@pytest.fixture(scope="session")
def counter_wasm():
"""Compile the counter contract."""
return compile_contract("counter_contract/__init__.py", single_file=True)
@pytest.fixture
def fresh_counter(sandbox, counter_wasm, temp_account):
"""Deploy a fresh counter contract for each test."""
return sandbox.deploy(
wasm_path=counter_wasm,
account=temp_account,
init_args={"starting_count": 0}
)
def test_increment_fresh(fresh_counter, sandbox_alice):
"""Test incrementing a fresh counter."""
# This always starts with count=0
result = fresh_counter.call("increment").as_transaction(sandbox_alice)
assert int(result) == 1
# Increment again
result = fresh_counter.call("increment").as_transaction(sandbox_alice)
assert int(result) == 2
def test_json_response(self):
# Call a method that returns JSON data
response = self.contract.call("get_user_data", {"user_id": "alice"})
# Parse the JSON response
data = response.json()
# Assert on the parsed data
assert data["name"] == "Alice"
assert data["score"] == 100
assert "created_at" in data
near-pytest
supports two testing styles:
- More pytest-native approach with composable fixtures
- Better test isolation with fixtures per test
- Method chaining for contract calls with clear semantics
- Easier to use with parallel testing (pytest-xdist)
- Traditional approach with a base class
- All test methods share setup
- State management with
save_state
andreset_state
near-pytest
automatically handles compilation of your Python smart contracts to WASM using the NEAR SDK for Python.
# Fixtures approach
wasm_path = compile_contract("path/to/contract.py", single_file=True)
# Class-based approach
wasm_path = cls.compile_contract("path/to/contract.py", single_file=True)
The compilation process includes:
- Automatic caching of compiled contracts (based on content hash)
- Support for single-file contracts or multi-file projects
- Seamless integration with the
nearc
compiler
The framework automatically:
- Downloads the appropriate NEAR sandbox binary for your platform
- Manages sandbox lifecycle (start/stop)
- Provides methods for state manipulation
Create test accounts easily:
# Fixtures approach
account = sandbox.create_account("alice")
random_account = sandbox.create_random_account()
# Class-based approach
cls.alice = cls.create_account("alice")
Deploy contracts to accounts and initialize them:
# Fixtures approach
contract = sandbox.deploy(
wasm_path=wasm_path,
account=account,
init_args={"param": "value"}
)
# Class-based approach
cls.contract = cls.deploy_contract(cls.account, wasm_path, init_args={"param": "value"})
Call contract methods:
# Fixtures approach (with method chaining)
result = contract.call("increment").as_transaction(account)
view_result = contract.call("get_count").as_view()
# Class-based approach
result = contract.call("increment", {})
view_result = contract.view("get_count", {})
Save and restore state for fast test execution:
# Fixtures approach
state = sandbox.save_state()
sandbox.reset_state(state)
# Class-based approach
cls.save_state()
self.reset_state()
Contract call responses are wrapped in a ContractResponse
object that provides a familiar interface similar to Python's requests
library:
# Get the raw text response
text_content = response.text
# Parse JSON response
json_data = response.json()
# String representation
str_value = str(response) # Same as response.text
sandbox
: Main entry point for interacting with the NEAR sandboxsandbox_alice
,sandbox_bob
: Pre-created accounts for teststemp_account
: Creates a fresh random account for each testcreate_account
: Factory fixture to create accounts on demandnear_client
: Direct access to the NEAR client for RPC operations
create_account(name)
: Create a new account with the given namecreate_random_account(prefix="test")
: Create a new account with a random namedeploy(wasm_path, account, init_args=None, init_method="new")
: Deploy a contractsave_state()
: Save current blockchain statereset_state(state)
: Reset to a previously saved state
call(method_name, **kwargs)
: Create a contract call with method chainingaccount_id
: Property to get the contract account ID
as_transaction(account, amount=0, gas=None)
: Execute as a transactionas_view()
: Execute as a view call
compile_contract(contract_path, single_file=False)
: Helper function to compile a contract to WASM
If you prefer to use the NEAR compiler (nearc) directly for more control over the compilation process:
import nearc
# Compile your contract with custom options
wasm_path = nearc.builder.compile_contract(
contract_path="path/to/contract.py",
output_path="path/to/output.wasm",
single_file=True
)
Our fixtures use a naming convention to distinguish between different networks:
sandbox
- The main fixture that provides access to the NEAR sandbox nodelocalnet_alice
,localnet_bob
,localnet_temp_account
- Account fixtures that operate on the local network
This distinction allows for future expansion to other networks like testnet or mainnet while maintaining a clear separation of concerns.
setup_class(cls)
: Set up shared resources for the test classcompile_contract(contract_path, single_file=False)
: Compile a contract to WASMcreate_account(name, initial_balance=None)
: Create a new test accountdeploy_contract(account, wasm_path, init_args=None)
: Deploy a contractsave_state()
: Save the current state for later resetreset_state()
: Reset to the previously saved state
call_contract(contract_id, method_name, args=None, amount=0, gas=None)
: Call a contract methodview_contract(contract_id, method_name, args=None)
: Call a view methoddeploy_contract(wasm_file)
: Deploy a contract to this account
call(method_name, args=None, amount=0, gas=None)
: Call as the contract accountcall_as(account, method_name, args=None, amount=0, gas=None)
: Call as another accountview(method_name, args=None)
: Call a view method
A wrapper for contract call responses that provides a familiar interface for handling response data.
text
: Get the raw text response as a stringtransaction_result
: Access the underlying transaction result (if available)
json()
: Parse the response as JSON and return the resulting Python object
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
You can customize the behavior of near-pytest using these environment variables:
NEAR_PYTEST_LOG_LEVEL
: Set logging level (DEBUG, INFO, WARNING, ERROR)NEAR_SANDBOX_HOME
: Specify a custom home directory for the sandbox
near-pytest consists of several core components:
- SandboxManager: Handles the NEAR sandbox process lifecycle
- NearClient: Manages communication with the NEAR RPC interface
- Account/Contract: Simplified models for interacting with the blockchain
- ContractResponse: Wraps contract call responses with a user-friendly API
- NearTestCase: Base class that ties everything together for testing
-
Sandbox doesn't start
- Check if port 3030 is available
- Ensure you have proper permissions to execute downloaded binaries
-
Contract compilation fails
- Verify that the nearc package is installed
- Check Python version compatibility (3.11+ required)
-
Slow test execution
- Ensure you're using
save_state()
andreset_state()
pattern - Verify if cache directory (~/.near-pytest/cache) exists and is writeable
- Ensure you're using
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.