Testing StxScript Contracts¶
StxScript includes a testing framework for writing and running contract tests.
Quick Start¶
Create a Test¶
Create tests/test_contract.py:
from stxscript.testing import ContractTestCase, ClarityValue, Assertions
class TestMyContract(ContractTestCase):
def setUp(self):
self.load_contract_file('src/main.stx')
def test_mint_success(self):
result = ClarityValue.ok(ClarityValue.bool(True))
Assertions.is_ok(result)
def test_get_balance(self):
expected = ClarityValue.uint(1000)
actual = ClarityValue.uint(1000)
Assertions.equals(actual, expected)
Run Tests¶
Test Framework¶
ContractTestCase¶
Base class for all contract tests.
from stxscript.testing import ContractTestCase
class TestMyContract(ContractTestCase):
def setUp(self):
# Load contract source
self.load_contract_file('contract.stx')
# Or load from string
self.load_contract('const X: uint = 1u;')
def tearDown(self):
# Cleanup (automatic)
pass
def test_something(self):
# Your test
pass
ClarityValue¶
Create Clarity values for testing.
from stxscript.testing import ClarityValue
# Primitives
integer = ClarityValue.int(-42)
unsigned = ClarityValue.uint(100)
boolean = ClarityValue.bool(True)
string = ClarityValue.string("Hello")
principal = ClarityValue.principal("'SP2J...")
# Optionals
some_value = ClarityValue.some(ClarityValue.uint(42))
none_value = ClarityValue.none()
# Responses
ok_result = ClarityValue.ok(ClarityValue.bool(True))
err_result = ClarityValue.err(ClarityValue.uint(1))
# Collections
list_value = ClarityValue.list([
ClarityValue.uint(1),
ClarityValue.uint(2)
])
tuple_value = ClarityValue.tuple({
"name": ClarityValue.string("Alice"),
"balance": ClarityValue.uint(100)
})
Assertions¶
Assert conditions on Clarity values.
from stxscript.testing import Assertions
# Response assertions
Assertions.is_ok(result)
Assertions.is_err(result)
Assertions.ok_equals(result, expected_value)
Assertions.err_equals(result, expected_value)
# Optional assertions
Assertions.is_some(optional)
Assertions.is_none(optional)
# Equality
Assertions.equals(actual, expected)
Mock Blockchain¶
Simulate blockchain state.
from stxscript.testing import ContractTestCase, ClarityValue
class TestWithBlockchain(ContractTestCase):
def setUp(self):
self.load_contract_file('contract.stx')
# Set block height
self.blockchain.set_block_height(100)
# Set STX balances
self.blockchain.set_stx_balance(
"'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7",
1000000
)
# Set transaction sender
self.blockchain.set_tx_sender(
"'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7"
)
# Set data variables
self.blockchain.set_var(
"total_supply",
ClarityValue.uint(1000)
)
# Set map entries
self.blockchain.set_map_entry(
"balances",
"'SP2J...",
ClarityValue.uint(500)
)
def test_after_mining(self):
# Mine blocks
self.blockchain.mine_block(10)
# Block height is now 110
self.assertEqual(self.blockchain.block_height, 110)
Mock Contract Calls¶
Mock responses from other contracts.
from stxscript.testing import ContractTestCase, ClarityValue
class TestWithMocks(ContractTestCase):
def setUp(self):
self.load_contract_file('contract.stx')
# Set up mock response
self.mock_calls.when("Token", "transfer").returns(
ClarityValue.ok(ClarityValue.bool(True))
)
def test_calls_token(self):
# ... run test ...
# Verify the call was made
self.assertTrue(
self.mock_calls.verify_called("Token", "transfer")
)
# Verify call count
self.assertTrue(
self.mock_calls.verify_called("Token", "transfer", times=1)
)
Test Runner¶
Run tests programmatically.
from stxscript.testing import TestRunner
runner = TestRunner()
# Run a single test suite
result = runner.run_suite(TestMyContract)
# Run multiple suites
results = runner.run_all([TestMyContract, TestOtherContract])
# Print results
runner.print_results()
# Export as JSON
json_output = runner.to_json()
Coverage Tracking¶
Track code coverage.
from stxscript.testing import CoverageTracker
tracker = CoverageTracker()
# Register files
tracker.register_file('contract.stx', total_lines=100)
# Mark lines as covered
tracker.mark_covered('contract.stx', 10)
tracker.mark_covered('contract.stx', 15)
# Get coverage
percentage = tracker.get_coverage('contract.stx')
total = tracker.get_total_coverage()
# Print report
tracker.print_report()
CLI Usage¶
# Run all tests in tests/
stxscript test
# Specify directory
stxscript test tests/
# With coverage
stxscript test --coverage
# JSON output
stxscript test --json
# Specific pattern
stxscript test --pattern "test_token_*.py"
Example: Token Contract Tests¶
from stxscript.testing import (
ContractTestCase,
ClarityValue,
Assertions
)
class TestToken(ContractTestCase):
def setUp(self):
self.load_contract_file('src/token.stx')
# Set up initial state
self.blockchain.set_tx_sender(
"'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7"
)
self.blockchain.set_map_entry(
"balances",
"'SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7",
ClarityValue.uint(1000)
)
def test_transfer_success(self):
# Expected: transfer succeeds
result = ClarityValue.ok(ClarityValue.bool(True))
Assertions.is_ok(result)
def test_transfer_insufficient_balance(self):
# Expected: transfer fails with error code
result = ClarityValue.err(ClarityValue.uint(101))
Assertions.is_err(result)
Assertions.err_equals(result, ClarityValue.uint(101))
def test_get_balance(self):
expected = ClarityValue.uint(1000)
actual = ClarityValue.uint(1000)
Assertions.equals(actual, expected)
def test_mint_within_limit(self):
result = ClarityValue.ok(ClarityValue.bool(True))
Assertions.is_ok(result)
def test_mint_exceeds_limit(self):
result = ClarityValue.err(ClarityValue.uint(1))
Assertions.is_err(result)
Best Practices¶
- One test file per contract - Keep tests organized
- Use setUp for common state - Avoid repetition
- Test both success and failure - Cover error paths
- Use descriptive names -
test_transfer_insufficient_balance - Mock external contracts - Isolate your tests
- Check edge cases - Zero values, max values, boundaries