Pytest
The pytest framework allows for testing of contract deployments
and functions. This is something that comes built into cairo-lang
and it is best to see nile setup
first to get contract for the framework.
Minimal Test Template
Make a contract TEMPLATE.cairo that has:
- One @externalfunction,EXTERNAL_FUNCTION_NAME()- If should accept two inputs, INPUT_1andINPUT_2
 
- If should accept two inputs, 
- One @viewfunction,VIEW_FUNCTION_NAME()- If should return one input, VAL.
 
- If should return one input, 
Make a new file called TEMPLATE_contract_test.py and populate it:
import pytest
from starkware.starknet.testing.starknet import Starknet
from starkware.starknet.testing.contract import StarknetContract
# Make sure that tests start with 'test_'.
@pytest.mark.asyncio
async def test_main_logic():
    # Create the local network
    starknet = await Starknet.empty()
    # Deploy the contract
    contract = await starknet.deploy("contracts/TEMPLATE.cairo")
    # Modify a contract.
    await contract.EXTERNAL_FUNCTION_NAME(INPUT_1, INPUT2).invoke()
    # Read from a contract
    VAL = await contract.VIEW_FUNCTION_NAME().call()
    assert VAL == EXPECTED_RESULT
    print('Value is as expected')
The packages required for testing are installed by default with cairo-lang. Run the test:
pytest TEMPLATE_contract_test.py
# Show print statements during testing.
pytest -s TEMPLATE_contract_test.py
# Run a specific test.
pytest TEMPLATE_contract_test.py::test_main_logic
Deployment factory
We move the deployment to a reusable function that two separate tests can share.
import pytest
from starkware.starknet.testing.starknet import Starknet
from starkware.starknet.testing.contract import StarknetContract
# Enables modules.
@pytest.fixture(scope='module')
def event_loop():
    return asyncio.new_event_loop()
# Reusable to save testing time.
@pytest.fixture(scope='module')
async def contract_factory():
    starknet = await Starknet.empty()
    contract = await starknet.deploy("contracts/TEMPLATE.cairo")
    return starknet, contract
@pytest.mark.asyncio
async def test_main_logic(contract_factory):
    starknet, contract = contract_factory
    # Modify a contract.
    await contract.EXTERNAL_FUNCTION_NAME(INPUT_1, INPUT2).invoke()
    # Read from a contract
    VAL = await contract.VIEW_FUNCTION_NAME().call()
    assert VAL == EXPECTED_RESULT
    print('Value is as expected')
# A second test function that uses the same deployments.
@pytest.mark.asyncio
async def test_two(contract_factory):
    starknet, contract = contract_factory
    VAL = await contract.VIEW_FUNCTION_NAME().call()
    assert VAL == EXPECTED_RESULT
Constructor arguments
If the contract has a @constructor function that accepts
arguments on dpeloyment, they are passed as follows:
contract = await starknet.deploy(
    "contracts/TEMPLATE.cairo",
    constructor_calldata=[ARG_1, ARG_2]
    )
Account based testing
Accounts in StarkNet differ from Ethereum mainnet in that StarkNet has Account Abstraction. Every transaction originates from an account contract
# Every user must deploy and initialise an account.
# Initialisation involves saving some key(s), such as a single
# public key.
Account.cairo
# All transactions to application contracts are passed to the
# Account for verification before forwarding on to the application.
More about account abstraction in StarkNet can be read here and here.
In the testing framework you can test account based transaction origination, or you can interact with contracts directly. On Mainnet StarkNet, you will have to use a account.
The principle for testing with a single-owner account in pytest is as follows:
- Select an account contract
- Use a helper function to create a Signerobject that has a private key
- Deploy the account and save the public key in it
- Determine the payload for your application
- Send a transaction to the account containing a signed payload
One good resource is the OpenZeppelin signer and account
- signer.py
    - Copy this file into a /tests/utilsdirectory.
- Import the signer into pytest file from test.utils.signer import Signer
 
- Copy this file into a 
- Account.cairo
    - Copy this file into the /contractsdirectory.
 
- Copy this file into the 
How to use an account:
import pytest
from starkware.starknet.testing.starknet import Starknet
from starkware.starknet.testing.contract import StarknetContract
from utils.Signer import Signer
signer = Signer(123456789987654321)
other = Signer(987654321123456789)
# Enables modules.
@pytest.fixture(scope='module')
def event_loop():
    return asyncio.new_event_loop()
@pytest.fixture(scope='module')
async def contract_factory():
    starknet = await Starknet.empty()
    contract = await starknet.deploy("contracts/TEMPLATE.cairo")
    # Deploy the account
    first_account = await starknet.deploy(
        "contracts/Account.cairo",
        constructor_calldata=[signer.public_key]
    )
    second_account = await starknet.deploy(
        "contracts/Account.cairo",
        constructor_calldata=[other.public_key]
    )
    return starknet, contract, first_account, second_account
@pytest.mark.asyncio
async def test_main_logic(contract_factory):
    starknet, contract, first_account, second_account = contract_factory
    # The signer module will then handle invoking the account contract
    # 'execute()' function and also handle the account nonce.
    await signer.send_transaction(
        account=first_account,
        to=contract,
        selector_name='EXTERNAL_FUNCTION_NAME',
        calldata=[arg_1, arg_2])
    # The application contract can now use the 'get_caller_address()'
    # function from the cairo common library to perform checks on
    # the account making the transaction.