# Smart Contract Testing

This guide provides an overview of how to test smart contracts using the Algorand Python SDK (`algopy`). We will cover the basics of testing `ARC4Contract` and `Contract` classes, focusing on `abimethod` and `baremethod` decorators.

```
The code snippets showcasing the contract testing capabilities are using [pytest](https://docs.pytest.org/en/latest/) as the test framework. However, note that the `algorand-python-testing` package can be used with any other test framework that supports Python. `pytest` is used for demonstration purposes in this documentation.
```

```
import algopy

import algopy_testing

from algopy_testing import algopy_testing_context

# Create the context manager for snippets below

ctx_manager = algopy_testing_context()

# Enter the context

context = ctx_manager.__enter__()
```

The following video includes a practical tutorial of how to do tests in Algorand Smart Contracts:

[Smart Contract Testing - Python](https://www.youtube.com/watch?v=B4mzNmQB5mU)

## `algopy.ARC4Contract`

Subclasses of `algopy.ARC4Contract` are **required** to be instantiated with an active test context. As part of instantiation, the test context will automatically create a matching `algopy.Application` object instance.

Within the class implementation, methods decorated with `algopy.arc4.abimethod` and `algopy.arc4.baremethod` will automatically assemble an `algopy.gtxn.ApplicationCallTransaction` transaction to emulate the AVM application call. This behavior can be overridden by setting the transaction group manually as part of test setup, this is done via implicit invocation of `algopy_testing.context.any_application()` _value generator_ (refer to [APIs](https://dev.algorand.co/algokit/unit-testing/python/apis) for more details).

```
class SimpleVotingContract(algopy.ARC4Contract):

def __init__(self) -> None:

self.topic = algopy.GlobalState(algopy.Bytes(b"default_topic"), key="topic", description="Voting topic")

self.votes = algopy.GlobalState(

algopy.UInt64(0),

key="votes",

description="Votes for the option",

)

self.voted = algopy.LocalState(algopy.UInt64, key="voted", description="Tracks if an account has voted")

@algopy.arc4.abimethod(create="require")

def create(self, initial_topic: algopy.Bytes) -> None:

self.topic.value = initial_topic

self.votes.value = algopy.UInt64(0)

@algopy.arc4.abimethod

def vote(self) -> algopy.UInt64:

assert self.voted[algopy.Txn.sender] == algopy.UInt64(0), "Account has already voted"

self.votes.value += algopy.UInt64(1)

self.voted[algopy.Txn.sender] = algopy.UInt64(1)

return self.votes.value

@algopy.arc4.abimethod(readonly=True)

def get_votes(self) -> algopy.UInt64:

return self.votes.value

@algopy.arc4.abimethod

def change_topic(self, new_topic: algopy.Bytes) -> None:

assert algopy.Txn.sender == algopy.Txn.application_id.creator, "Only creator can change topic"

self.topic.value = new_topic

self.votes.value = algopy.UInt64(0)

# Reset user's vote (this is simplified per single user for the sake of example)

self.voted[algopy.Txn.sender] = algopy.UInt64(0)

# Arrange

initial_topic = algopy.Bytes(b"initial_topic")
contract = SimpleVotingContract()
contract.voted[context.default_sender] = algopy.UInt64(0)

# Act - Create the contract

contract.create(initial_topic)

# Assert - Check initial state

assert contract.topic.value == initial_topic
assert contract.votes.value == algopy.UInt64(0)

# Act - Vote

# The method `.vote()` is decorated with `algopy.arc4.abimethod`, which means it will assemble a transaction to emulate the AVM application call

result = contract.vote()

# Assert - you can access the corresponding auto generated application call transaction via test context

assert len(context.txn.last_group.txns) == 1

# Assert - Note how local and global state are accessed via regular python instance attributes

assert result == algopy.UInt64(1)
assert contract.votes.value == algopy.UInt64(1)
assert contract.voted[context.default_sender] == algopy.UInt64(1)

# Act - Change topic

new_topic = algopy.Bytes(b"new_topic")
contract.change_topic(new_topic)

# Assert - Check topic changed and votes reset

assert contract.topic.value == new_topic
assert contract.votes.value == algopy.UInt64(0)
assert contract.voted[context.default_sender] == algopy.UInt64(0)

# Act - Get votes (should be 0 after reset)

votes = contract.get_votes()

# Assert - Check votes

assert votes == algopy.UInt64(0)
```

For more examples of tests using `algopy.ARC4Contract`, see the [examples](https://dev.algorand.co/algokit/unit-testing/python/examples) section.

## `algopy.Contract`

Subclasses of `algopy.Contract` are **required** to be instantiated with an active test context. As part of instantiation, the test context will automatically create a matching `algopy.Application` object instance. This behavior is identical to `algopy.ARC4Contract` class instances.

Unlike `algopy.ARC4Contract`, `algopy.Contract` requires manual setup of the transaction context and explicit method calls. Alternatively, you can use `active_txn_overrides` to specify application arguments and foreign arrays without needing to create a full transaction group if your aim is to patch a specific active transaction related metadata.

Here’s an updated example demonstrating how to test a `Contract` class:

```
import algopy

import pytest

from algopy_testing import AlgopyTestContext, algopy_testing_context

class CounterContract(algopy.Contract):

def __init__(self):

self.counter = algopy.UInt64(0)

@algopy.subroutine

def increment(self):

self.counter += algopy.UInt64(1)

return algopy.UInt64(1)

@algopy.arc4.baremethod

def approval_program(self):

return self.increment()

@algopy.arc4.baremethod

def clear_state_program(self):

return algopy.UInt64(1)

@pytest.fixture()

def context():

with algopy_testing_context() as ctx:

yield ctx

def test_counter_contract(context: AlgopyTestContext):

# Instantiate contract

contract = CounterContract()

# Set up the transaction context using active_txn_overrides

with context.txn.create_group(

active_txn_overrides={

"sender": context.default_sender,

"app_args": [algopy.Bytes(b"increment")],

}

):

# Invoke approval program

result = contract.approval_program()

# Assert approval program result

assert result == algopy.UInt64(1)

# Assert counter value

assert contract.counter == algopy.UInt64(1)

# Test clear state program

assert contract.clear_state_program() == algopy.UInt64(1)

def test_counter_contract_multiple_txns(context: AlgopyTestContext):

contract = CounterContract()

# For scenarios with multiple transactions, you can still use gtxns

extra_payment = context.any.txn.payment()

with context.txn.create_group(

gtxns=[

extra_payment,

context.any.txn.application_call(

sender=context.default_sender,

app_id=contract.app_id,

app_args=[algopy.Bytes(b"increment")],

),

],

active_txn_index=1  # Set the application call as the active transaction

):

result = contract.approval_program()

assert result == algopy.UInt64(1)

assert contract.counter == algopy.UInt64(1)

assert len(context.txn.last_group.txns) == 2
```

In this updated example:

1. We use `context.txn.create_group()` with `active_txn_overrides` to set up the transaction context for a single application call. This simplifies the process when you don’t need to specify a full transaction group.

2. The `active_txn_overrides` parameter allows you to specify `app_args` and other transaction fields directly, without creating a full `ApplicationCallTransaction` object.

3. For scenarios involving multiple transactions, you can still use the `gtxns` parameter to create a transaction group, as shown in the `test_counter_contract_multiple_txns` function.

4. The `app_id` is automatically set to the contract’s application ID, so you don’t need to specify it explicitly when using `active_txn_overrides`.

This approach provides more flexibility in setting up the transaction context for testing `Contract` classes, allowing for both simple single-transaction scenarios and more complex multi-transaction tests.

## Defer contract method invocation

You can create deferred application calls for more complex testing scenarios where order of transactions needs to be controlled:

```
def test_deferred_call(context):

contract = MyARC4Contract()

extra_payment = context.any.txn.payment()

extra_asset_transfer = context.any.txn.asset_transfer()

implicit_payment = context.any.txn.payment()

deferred_call = context.txn.defer_app_call(contract.some_method, implicit_payment)

with context.txn.create_group([extra_payment, deferred_call, extra_asset_transfer]):

result = deferred_call.submit()

print(context.txn.last_group) # [extra_payment, implicit_payment, app call, extra_asset_transfer]
```

A deferred application call prepares the application call transaction without immediately executing it. The call can be executed later by invoking the `.submit()` method on the deferred application call instance. As demonstrated in the example, you can also include the deferred call in a transaction group creation context manager to execute it as part of a larger transaction group. When `.submit()` is called, only the specific method passed to `defer_app_call()` will be executed.
