Skip to content
On this page

Test scenario

The test scenario is essential for testing. It lets you work with contracts, calculate expressions, set flags, and more. Essentially, it mimics the Tezos blockchain for testing to ensure contracts work correctly before deployment.

WARNING

The test scenario must be defined before any instantiation as the instance is using the scenario to pre-compile the contract.

sp.test_scenario(name: str, modules: list[sp.module] | sp.module) → test_scenario

Returns a test scenario.

There must be at most one test scenario per test function and it should be defined as the first instruction.

python
@sp.add_test()
def test():
    # Create a test scenario
    sc = sp.test_scenario("A Test", main)

Exceptions

sp.is_failing(expression) → 

Returns True when an expression (views, lambdas, ...) results in failure, and False when the expression succeeds.

Content of exceptions

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

t is optional, just using sp.catch_exception(<expression>) will be 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 will contain sp.Some(<exception>) when the expression fails or None if it succeeds.

Views

Views, both off-chain or on-chain, can now be called from test scenarios the same way as entrypoints. The example below shows how to do it.

Example

python
import smartpy as sp

@sp.module
def main():
  class MyContract(sp.Contract):
      def __init__(self, param):
          self.data = param

      @sp.entrypoint
      def myEntrypoint(self, n):
         self.data = n

      @sp.offchain_view()
      def state(self, param):
          assert param < 5, "This is false: param > 5"
          return self.data * param

@sp.add_test()
def test():
    scenario = sp.test_scenario("Minimal", main)
    c1 = main.MyContract(1)
    scenario += c1

    """ Test views """

    # Display the offchain call result
    scenario.show(c1.state(1))

    # Assert the view result
    scenario.verify(c1.state(2) == 2)
    # Assert call failures
    scenario.verify(sp.is_failing(c1.state(6)));    # Expected to fail
    scenario.verify(~ sp.is_failing(c1.state(1)));   # Not expected to fail

    # Assert exception result
    # catch_exception returns an option:
    #      None if the call succeeds
    #      sp.Some(<exception>) if the call fails
    e = sp.catch_exception(c1.state(7), t = sp.string)
    scenario.verify(e == sp.Some("This is false: param > 5"))

Document extra information

sc.h<N>(content: str) → 

Add a section heading of the level <N>.

<h1> is the highest section level.

python
scenario.h1("a title")
scenario.h2("a subtitle")
scenario.h3('Equivalent to <h3> HTML tag.')
scenario.h4("Equivalent to <h4> HTML tag.")
sc.p(content: str) → 

Add a text paragraph to the scenario equivalent to <p>.

python
scenario.p("Equivalent to <p> HTML tag.")

Expressions

Showing expressions

sc.show(expression, html = True) → 

To show expressions, we use scenario.show(expression, html = True).

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

scenario.show(c1.data.myParameter1 * 12)
scenario.show(c1.data)

Computing expressions

sc.compute(expression, **context_args) → t

To compute expressions, we use scenario.compute(<expression>, **context_args).

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

The variable x can now be used in the sequel of the scenario and its value is fixed.

Compute optional arguments

Generic context arguments are:

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.

Assertions

sc.verify(expression) → 

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

python
scenario.verify(c1.data.myParameter == 51)
sc.verify_equal(expr1, expr2) → 

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

python
scenario.verify_equal(c1.data.myList, [2, 3, 5, 7])

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.

sc.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)