All skills

Develops Algorand smart contracts in Python using PuyaPy — covers syntax, decorators, storage, transactions, types, testing with pytest, deployment, AlgoKit Utils, ARC-4/ARC-56 standards, and error troubleshooting. Use when writing algopy contracts, using @arc4.abimethod decorators, working with GlobalState or BoxMap in Python, testing with AlgorandClient, deploying or calling contracts from Python, or diagnosing PuyaPy compiler and transaction errors.

Use this Skill: https://skilld.dev/gh/algorand-devrel/algorand-agent-skills/algorand-python

This session only. Nothing lands on disk.

referencestesting.md

≈3.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Testing Algorand Python Contracts

Contents

Write tests for Algorand Python contracts using pytest and AlgoKit Utils.

Default: Integration Tests

Always write integration tests unless explicitly asked for unit tests. Integration tests run against LocalNet and test real contract behavior.

File Naming

  • Integration tests: test_contract.py or test_contract_e2e.py
  • Unit tests (only if requested): test_contract_unit.py

Canonical Example

Study and adapt from: devportal-code-examples/contracts/HelloWorld

import pytest
from algokit_utils import AlgorandClient, AlgoAmount
from artifacts.my_contract_client import MyContractFactory


@pytest.fixture
def algorand() -> AlgorandClient:
    """Create AlgorandClient connected to LocalNet."""
    return AlgorandClient.default_localnet()


@pytest.fixture
def deployer(algorand: AlgorandClient):
    """Get a funded deployer account."""
    account = algorand.account.random()
    algorand.account.ensure_funded(
        account.address,
        AlgoAmount(algo=10),
    )
    return account


@pytest.fixture
def app_client(algorand: AlgorandClient, deployer):
    """Deploy contract and return typed client."""
    factory = algorand.client.get_typed_app_factory(
        MyContractFactory,
        default_sender=deployer.address,
    )
    result = factory.deploy(
        on_update="append",
        on_schema_break="append",
    )
    return result.app_client


def test_my_method(app_client):
    """Test calling a contract method."""
    result = app_client.send.my_method(value=42)
    assert result.abi_return == 42

BoxStorage E2E Example

import pytest
from algokit_utils import AlgorandClient, AlgoAmount
from artifacts.box_storage_client import BoxStorageFactory


@pytest.fixture
def algorand() -> AlgorandClient:
    return AlgorandClient.default_localnet()


@pytest.fixture
def deployer(algorand: AlgorandClient):
    account = algorand.account.random()
    algorand.account.ensure_funded(account.address, AlgoAmount(algo=10))
    return account


@pytest.fixture
def app_client(algorand: AlgorandClient, deployer):
    factory = algorand.client.get_typed_app_factory(
        BoxStorageFactory,
        default_sender=deployer.address,
    )
    result = factory.deploy(on_update="append", on_schema_break="append")
    return result.app_client


def fund_contract(algorand, sender, app_client):
    """CRITICAL: Fund app account before any box operations."""
    algorand.send.payment(
        sender=sender.address,
        receiver=app_client.app_address,
        amount=AlgoAmount(algo=1),
    )


def test_set_and_read_box(algorand, deployer, app_client):
    """Test setting and reading a box value."""
    fund_contract(algorand, deployer, app_client)

    app_client.send.set_box(value_int=42)

    # Read box state via typed client
    box_value = app_client.state.box.box_int()
    assert box_value == 42


def test_set_and_read_box_map(algorand, deployer, app_client):
    """Test BoxMap with key-value pairs."""
    fund_contract(algorand, deployer, app_client)

    app_client.send.set_box_map(key=1, value="Hello")

    result = app_client.send.get_box_map(key=1)
    assert result.abi_return == "Hello"

LocalStorage E2E Example

import pytest
from algokit_utils import AlgorandClient, AlgoAmount
from artifacts.local_storage_client import LocalStorageFactory


@pytest.fixture
def algorand() -> AlgorandClient:
    return AlgorandClient.default_localnet()


@pytest.fixture
def deployer(algorand: AlgorandClient):
    account = algorand.account.random()
    algorand.account.ensure_funded(account.address, AlgoAmount(algo=10))
    return account


@pytest.fixture
def app_client(algorand: AlgorandClient, deployer):
    factory = algorand.client.get_typed_app_factory(
        LocalStorageFactory,
        default_sender=deployer.address,
    )
    result = factory.deploy(on_update="append", on_schema_break="append")
    return result.app_client


def test_opt_in_and_read_local_state(app_client):
    """MUST opt in before accessing local state."""
    app_client.send.opt_in.opt_in()

    result = app_client.send.read_local_state()
    assert result.abi_return is not None


def test_write_and_read_local_state(app_client):
    """Write local state then verify values."""
    app_client.send.opt_in.opt_in()

    app_client.send.write_local_state(
        value_string="Hello",
        value_bool=True,
    )

    result = app_client.send.read_local_state()
    assert result.abi_return is not None


def test_multi_user_local_state(algorand, deployer, app_client):
    """Each user has independent local state."""
    # User 1 opts in and writes
    app_client.send.opt_in.opt_in()
    app_client.send.write_local_state(value_string="Alice")

    # Create user 2
    user2 = algorand.account.random()
    algorand.account.ensure_funded(user2.address, AlgoAmount(algo=5))

    client2 = algorand.client.get_typed_app_client_by_id(
        LocalStorageFactory,
        app_id=app_client.app_id,
        default_sender=user2.address,
    )
    client2.send.opt_in.opt_in()
    client2.send.write_local_state(value_string="Bob")

    # Verify each user's state is independent
    local1 = app_client.state.local_state(deployer.address).get_all()
    local2 = app_client.state.local_state(user2.address).get_all()
    assert local1 != local2

Common Patterns

Fund Contract for Box Storage

def test_box_storage(algorand, app_client, deployer):
    # Fund app account for box MBR
    algorand.send.payment(
        sender=deployer.address,
        receiver=app_client.app_address,
        amount=AlgoAmount(algo=1),
    )

    # Now box operations will succeed
    app_client.send.set_box(key=1, value="hello")

Multiple Users

def test_multi_user(algorand, app_client, deployer):
    # Create and fund second user
    user2 = algorand.account.random()
    algorand.account.ensure_funded(
        user2.address,
        AlgoAmount(algo=5),
    )

    # Get client for same app with different sender
    client2 = algorand.client.get_typed_app_client_by_id(
        MyContractFactory,
        app_id=app_client.app_id,
        default_sender=user2.address,
    )

    # Opt in as user2
    client2.send.opt_in.opt_in()

Testing with Transaction Groups

def test_grouped_calls(app_client):
    result = (
        app_client.new_group()
        .method1(arg1="hello")
        .method2(arg2=42)
        .send()
    )
    assert result.returns[0].value == "expected"

Error Testing

import pytest
from algokit_utils import LogicError


def test_unauthorized_access(app_client):
    """Test that unauthorized calls are rejected."""
    with pytest.raises(LogicError, match="assert failed"):
        app_client.send.admin_only_method()


def test_invalid_input(app_client):
    """Test input validation."""
    with pytest.raises(LogicError):
        app_client.send.set_value(value=0)  # Contract requires value > 0

Simulation (Read-Only Calls)

def test_read_only_via_simulate(app_client):
    """Use simulate for read-only methods — no fees, no signing."""
    result = (
        app_client.new_group()
        .get_counter()
        .simulate()
    )
    counter_value = result.returns[0].value
    assert counter_value >= 0


def test_simulate_before_send(app_client):
    """Dry-run a state-changing call to check it would succeed."""
    result = (
        app_client.new_group()
        .increment(amount=1)
        .simulate()
    )
    # Verify it would succeed without actually changing state
    assert result.returns[0].value is not None

State Verification

def test_global_state(app_client):
    """Read and verify global state."""
    app_client.send.increment()
    app_client.send.increment()

    state = app_client.state.global_state.get_all()
    assert state["counter"] == 2

    # Or read a specific key
    counter = app_client.state.global_state.counter()
    assert counter == 2


def test_local_state(app_client, deployer):
    """Read and verify local state for an account."""
    app_client.send.opt_in.opt_in()
    app_client.send.set_nickname(name="Alice")

    local = app_client.state.local_state(deployer.address).get_all()
    assert local["nickname"] == "Alice"


def test_box_state(algorand, deployer, app_client):
    """Read and verify box state."""
    algorand.send.payment(
        sender=deployer.address,
        receiver=app_client.app_address,
        amount=AlgoAmount(algo=1),
    )
    app_client.send.set_box(value=42)

    box_value = app_client.state.box.my_box()
    assert box_value == 42

Key Patterns Summary

Pattern Code
Deploy result = factory.deploy(on_update="append", on_schema_break="append")
Send method result = app_client.send.method_name(arg1=value)
Chain methods app_client.new_group().method1().method2().send()
Simulate app_client.new_group().method().simulate()
Fund app algorand.send.payment(sender=deployer.address, receiver=app_client.app_address, amount=AlgoAmount(algo=1))
Opt-in app_client.send.opt_in.opt_in()
Error test with pytest.raises(LogicError, match="assert failed"): ...

Critical Rules

Rule Details
Use pytest fixtures Set up AlgorandClient and accounts as fixtures
Fund accounts Use ensure_funded for test accounts
Fund app for boxes Send payment to app_client.app_address before box operations
Opt-in before local state Call opt_in before reading/writing local state
Build before testing Run algokit project run build to generate typed clients

Running Tests

algokit project run build   # Build first (generates typed clients)
algokit project run test    # Run pytest

Unit Testing (only when explicitly requested)

Only use unit tests if the user explicitly requests them. Integration tests (E2E) are the default and recommended approach.

When to use unit tests

  • User explicitly says "unit test" or "offline test"
  • Testing pure contract logic without network interaction
  • Fast iteration during contract development

Key Differences from E2E Tests

Aspect E2E Tests Unit Tests
Framework pytest + AlgorandClient algopy_testing_context()
Network LocalNet Emulated AVM
Contract access Via typed client Direct instance
Method calls app_client.send.method(arg=val) contract.method(arg)
File naming test_contract.py test_contract_unit.py

Basic Setup

from algopy_testing import algopy_testing_context

def test_basic():
    with algopy_testing_context() as context:
        contract = MyContract()
        result = contract.my_method(arc4.UInt64(42))
        assert result == arc4.UInt64(42)

Testing with State

from algopy_testing import algopy_testing_context
from algopy import UInt64

def test_counter():
    with algopy_testing_context() as context:
        contract = CounterContract()
        contract.increment()
        contract.increment()
        assert contract.counter == UInt64(2)

Testing with Multiple Accounts

from algopy_testing import algopy_testing_context

def test_multi_account():
    with algopy_testing_context() as context:
        contract = MyContract()

        account1 = context.any.account()
        account2 = context.any.account()

        context.txn.sender = account1
        contract.do_something()

        context.txn.sender = account2
        contract.do_something_else()

Testing Boxes

from algopy_testing import algopy_testing_context
from algopy import Bytes

def test_box_operations():
    with algopy_testing_context() as context:
        contract = BoxContract()
        contract.set_box(Bytes(b"myKey"), Bytes(b"myValue"))

        stored = context.ledger.get_box(contract, Bytes(b"myKey"))
        assert stored == Bytes(b"myValue")

Testing Logic Signatures

from algopy_testing import algopy_testing_context
from algopy import Bytes

def test_logicsig():
    with algopy_testing_context() as context:
        with context.txn.create_group([
            context.any.txn.payment(),
        ]):
            result = context.execute_logicsig(
                my_logic_sig, Bytes(b"secret")
            )
        assert result is True

Unit Testing Documentation

Priority Repositories for Examples

Repository Path
algorandfoundation/devportal-code-examples projects/python-examples/contracts/
algorandfoundation/puya examples/
algorandfoundation/algokit-python-template Template test patterns

Source: SKILL.md on GitHub

1 warning1mo3 checks · Risk SAFE
  • Gen Agent Trust Hub1mo

    This skill provides a comprehensive guide for developing Algorand smart contracts using Python. It includes documentation for the PuyaPy framework, testing with pytest, and project management with AlgoKit. No high-risk security issues were identified; however, the skill's role in processing and compiling user-provided code constitutes a standard development attack surface.

  • Socket1mo

    No alerts

  • Snyk1mo

    Risk: MEDIUM · 1 issue

Signed by skilld at 774f8a8. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 weeks ago.

Activeupdated 6 months ago

README badge

README badge for algorand-devrel/algorand-agent-skills/algorand-python