Skip to content
On this page

Testing contracts

After you have created a test scenario as described in Test scenarios, you can originate (deploy) your contracts to it and test their operation.

TIP

All work with smart contracts must take place inside the test scenario. For example, you can't check the storage of a contract with a command like if contract.data.storagevalue == 5. Instead, you must evaluate the state of the contract within the scenario, as in scenario.verify(contract.data.storagevalue == 5).

Instantiating and originating contracts

To originate a contract within a test scenario, create an instance of the contract, which automatically calls its class's __init__() method. Then add it to the scenario with the += operator, as in this example:

smartpy
def main():
    class MyContract(sp.Contract):
        def __init__(self):
            pass

    class MyContract2(sp.Contract):
        def __init__(self, x):
            self.data.x = x
            pass

    class MyContract3(sp.Contract):
        def __init__(self, x, y):
            self.data.x = x
            self.data.y = y


@sp.add_test()
def test():
    scenario = sp.test_scenario(main, "A Test")
    c1 = main.MyContract()
    c2 = main.MyContract2(2)
    c3 = main.MyContract3(x=5, y=2)
    scenario += c1
    scenario += c2
    scenario += c3

Testing entrypoints

scenario.verify(expression) → 

To verify conditions within a scenario, use scenario.verify(<condition>).

smartpy
scenario.verify(contract.data.myParameter == 51)
scenario.verify_equal(expr1, expr2) → 

To verify an equality condition, use scenario.verify_equal(<left_expression>, <right_expression>) which works on both comparable and non-comparable types.

smartpy
scenario.verify_equal(contract.data.myList, [2, 3, 5, 7])

After you create an instance of a contract and add it to the scenario, you can call its entrypoints as methods on the contract object. Then you can use functions such as scenario.verify to verify things in the test scenario such as the current state of the contract storage. For example, this test scenario calls an entrypoint and verifies that its storage changed as expected:

smartpy
@sp.module
def main():

    class MyCounter(sp.Contract):

        def __init__(self, initialValue):
            sp.cast(initialValue, sp.nat)
            self.data.value = initialValue

        @sp.entrypoint
        def increment(self, update):
            self.data.value += update

@sp.add_test()
def test():
    # Create a test scenario
    # Specify the output folder and a module or list of modules to import
    scenario = sp.test_scenario("Test for my smart contract", main)

    # Create an instance of a contract
    # Automatically calls the __init__() method of the contract's class
    contract = main.MyCounter(5)
    # Add the contract to the scenario
    scenario += contract
    # Check the expected value in the contract storage
    scenario.verify(contract.data.value == 5)

    # Call an entrypoint
    contract.increment(3)
    # Check the expected value in the contract storage
    scenario.verify(contract.data.value == 8)

If the entrypoint accepts more than one parameter, you must pass the values as a record or as an implied record by naming the values, as in this example:

smartpy
scenario = sp.test_scenario(main, "A Test")
    contract = main.MyContract()
    scenario += contract

    # Implied record
    contract.exampleEntrypoint(a=5, b=6)
    # Explicit record
    contract.exampleEntrypoint(sp.record(a=5, b=6))

After the entrypoint parameters, you can include any of these context parameters to change how the entrypoint runs and is evaluated:

ParameterTypeAccessorDescription
_sendersp.address or sp.test_accountsp.senderThe simulated sender of the computation.
Specific to computation.
_sourcesp.address or sp.test_accountsp.sourceThe simulated source of the computation.
Specific to computation.
_chain_idsp.chain_idsp.chain_idThe simulated chain_id.
Preserved until changed.
_levelsp.natsp.levelThe simulated block level.
Preserved until changed.
_nowsp.timestampsp.nowThe simulated block timestamp.
Preserved until changed.
_voting_powerssp.map[sp.key_hash, sp.nat]sp.total_voting_power,
sp.voting_power
The simulated voting powers for the test.
Preserved until changed.
_amountsp.mutezsp.amountThe simulated amount of mutez to send with the smart contract call.
_validsp.boolNoneTells the interpreter if the transaction is expected to fail or not. True by default.
_exceptionany typeNoneThe expected exception raised by the transaction. If present, valid must be False.

For example, to test when an entrypoint throws an exception, pass _valid = False, as in this example:

smartpy
@sp.module
def main():ß

    class MyCounter(sp.Contract):
        def __init__(self, initialValue):
            sp.cast(initialValue, sp.nat)
            self.data.value = initialValue

        @sp.entrypoint
        def increment(self, update):
            assert update < 6, "Increment by less than 6"
            self.data.value += update

@sp.add_test()
def test():
    scenario = sp.test_scenario("Test for my smart contract", main)
    contract = main.MyCounter(5)
    scenario += contract

    # Call an entrypoint in a way that causes an exception
    contract.increment(10, _valid = False)
sp.catch_exception(expression, [t]) → sp.option[t]

Catches an exception in a expression.

The parameter t is optional; using sp.catch_exception(<expression>) is valid in most situations.

This method is used to test failing conditions of expressions (views, lambdas, ...). It returns an sp.option of type t that contain sp.Some(<exception>) when the expression fails or None if it succeeds.

Contract instance methods

In addition to methods for each entrypoint, contracts have these instance methods:

contract.get_source() → 

Returns the source code of the contract. See metadata.

contract.get_offchain_views() → 

Returns the off-chain views of the contract. See metadata.

contract.get_generated_michelson() → 

Returns the Michelson code the contract. See metadata.

contract.get_error_map() → 

Returns the contract's error map. See metadata and "exceptions" flag.

These instance methods can be called only before adding the contract to the scenario:

contract.set_initial_balance(balance: sp.tez) → 

Set the initial balance for the contract.

python
contract.set_initial_balance(sp.tez(20))
contract.data  x: any → 
Replace a field in the contract's storage.

This is mostly useful to initialize the metadata big map. See metadata.

Contract data

When a variable contract represents a contract in a scenario, we can access some associated data:

  • contract.data: Contract storage

  • contract.balance: Contract balance

  • contract.baker: Contract optional delegated baker

  • contract.address: Contract address within the scenario

    Details

    In storage or similar circumstances, deployed contracts get addresses of the form:

    • KT1TezoooozzSmartPyzzSTATiCzzzwwBFA1
    • KT1Tezooo1zzSmartPyzzSTATiCzzzyfC8eF
    • KT1Tezooo2zzSmartPyzzSTATiCzzzwqqQ4H
    • KT1Tezooo3zzSmartPyzzSTATiCzzzseJjWC
    • KT1Tezooo4zzSmartPyzzSTATiCzzzyPVdv3
    • KT1Tezooo5zzSmartPyzzSTATiCzzzz48Z4p
    • KT1Tezooo6zzSmartPyzzSTATiCzzztY1196
    • KT1Tezooo7zzSmartPyzzSTATiCzzzvTbG1z
    • KT1Tezooo8zzSmartPyzzSTATiCzzzzp29d1
    • KT1Tezooo9zzSmartPyzzSTATiCzzztdBMLX
    • KT1Tezoo1ozzSmartPyzzSTATiCzzzw8CmuY
    • ...
  • contract.typed

    Retrieve its testing typed contract value.

    To access entrypoints, you can use field notation:

    • contract.typed.my_entrypoint: Typed entrypoint my_entrypoint of contract contract.

Testing expressions (views and lambdas)

Testing views and lambdas is different from testing entrypoints because views and lambdas return SmartPy expressions. SmartPy does not evaluate these expressions until you tell it to.

sp.is_failing(expression) → 

Returns True when an expression results in failure and False when the expression succeeds.

sp.catch_exception(expression, [t]) → sp.option[t]

Evaluates an expression, catches any failures in it, and returns an option.

This method is used to test failing conditions of expressions. It returns an sp.option of type t that contains sp.Some(<exception>) when the expression fails or None when it succeeds. The t parameter is optional; in most cases you can leave it out.

scenario.show(expression, html = True) → 

Evaluate an expression and writes its result to the log.

ParameterTypeDescription
htmlboolTrue by default, False to export not in HTML but in source code format.
python
scenario.show(expression, html = True)

scenario.show(contract.data.myParameter1 * 12)
scenario.show(contract.data)
scenario.compute(expression, **context_args) → t

Evaluate an expression and return its result.

smartpy
x = scenario.compute(c1.data.myParameter1 * 12)

This example calls an on-chain view and then evaluates the expression it returns:

smartpy
@sp.module
def main():

    class MyCounter(sp.Contract):

        def __init__(self, initialValue):
            sp.cast(initialValue, sp.nat)
            self.data.value = initialValue

        @sp.onchain_view
        def getValue(self):
            return self.data.value

@sp.add_test()
def test():

    scenario = sp.test_scenario("view_test", main)
    contract = main.MyCounter(5)
    scenario += contract

    expression = contract.getValue()
    scenario.show(expression)
    scenario.verify(expression == 5)

This example tests an expression that fails. Note that the creation of the expression does not fail because SmartPy has not evaluated the expression yet. Then the test scenario tests the failure, which implicitly evaluates the expression.

smartpy
@sp.module
def main():

    class MyContract(sp.Contract):

        @sp.onchain_view
        def alwaysFails(self):
            assert 1 == 2, "Failure"

@sp.add_test()
def test():

    scenario = sp.test_scenario("expression_test", main)
    contract = main.MyContract()
    scenario += contract

    expression = contract.alwaysFails() # No exception yet
    scenario.verify(sp.is_failing(expression))
    scenario.verify(
        sp.catch_exception(expression, sp.string) == sp.Some("Failure")
    )

Testing dynamic contracts

Contracts in SmartPy can be created in two ways, statically as shown above, or dynamically via a call to the [sp.create_contract] statement from within an entrypoint.

We can refer to a contract that was created dynamically using scenario.dynamic_contract(<module>.<Contract>, offset), this returns a handle that can then be used as for static contracts.

scenario.dynamic_contract(template_ref: sp.Contract, offset:int | None) → sp.contract[t]

Returns a handle to the dynamic contract of class type template_ref=<module>.<Contract> that was created at offset.

The template_ref is used to check that the referenced contract has the correct class type.

The offset, if given, specifies the position in the list of dynamic contracts created so far. So offset=0 would refer to the first dynamically created contract and offset=-1 would refer to the most recently created dynamic contract. If not given it will default to the most recently created dynamic contract.

For example, to refer to the most recently created dynamic contract and check the class type is main.MyContract, we would use

python
dyn = scenario.dynamic_contract(main.MyContract)

Where-as

python
dyn = scenario.dynamic_contract(main.MyContract, offset=-2)

will refer to the last but one dynamic contract and check the class type is main.MyContract.

The handle dyn can now be used to call entrypoints, verify data etc

python
dyn.myEntrypoint(3)
scenario.verify(dyn.data == 3)