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, andFalse
when the expression succeeds.
Content of exceptions
- sp.catch_exception(expression, [t]) → sp.option[t]
t
is optional, just usingsp.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 containsp.Some(<exception>)
when the expression fails orNone
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
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.pythonscenario.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>
.pythonscenario.p("Equivalent to <p> HTML tag.")
Expressions
Showing expressions
- sc.show(expression, html = True)
To show expressions, we use
scenario.show(expression, html = True)
.Parameter Type Description html bool True
by default,False
to export not in html but like in source code.pythonscenario.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)
.pythonx = 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:
Parameter | Type | Accessor | Description |
---|---|---|---|
sender | sp.address or sp.test_account | sp.sender | The simulated sender of the computation. Specific to computation. |
source | sp.address or sp.test_account | sp.source | The simulated source of the computation. Specific to computation. |
chain_id | sp.chain_id | sp.chain_id | The simulated chain_id. Preserved until changed. |
level | sp.nat | sp.level | The simulated block level. Preserved until changed. |
now | sp.timestamp | sp.now | The simulated block timestamp. Preserved until changed. |
voting_powers | sp.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>)
.pythonscenario.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.pythonscenario.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 atoffset
.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. Sooffset=0
would refer to the first dynamically created contract andoffset=-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 usepythondyn = scenario.dynamic_contract(main.MyContract)
Where-as
pythondyn = 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 etcpythondyn.myEntrypoint(3) scenario.verify(dyn.data == 3)