Testing a contract
Once we have written our contracts, or as we are writing them, it is good to check that they meet our expectations, that they do what they are supposed to do.
There are multiple ways of doing this, but in this section we limit ourselves to testing. Testing involves the execution of our contract (or part of it) to check if certain property holds, and it can be manual or automated.
When we want to test a contract written in LIGO, we have two options:
we compile our code to Michelson and test the compiled code
we use LIGO specific tools to test our LIGO code
#
Testing Michelson codeThere are multiple frameworks for testing Michelson contracts, we will not get into details, but here is a list of tutorials showing how to test contracts in Michelson:
Another alternative is to use Tezos's binary tezos-client
directly. There's a new
mockup mode which is does
not need a Tezos node to be running (albeit this is less similar to
mainnet than running a Tezos sandboxed node).
tezos-client
's mockup#
Testing with We show the main steps that need to be done to use the mockup mode to test our LIGO contracts. As a first step, we need to compile our LIGO contract to Michelson code. Suppose we write the following simple contract:
To obtain Michelson code from it, we run the LIGO compiler:
Instead of outputting the resulted compiled code in the screen, we can
tell LIGO to write it in a file called mockup_testme.tz
:
Now it is time to test this Michelson code we obtained: we want to execute it using the mockup mode.
Before anything, make sure you have installed tezos-client
, a simple
way to do so is by using opam (opam install tezos-client
).
We can list all the protocols available using tezos-client list
mockup protocols
. In this example, we will use Edo for testing, so
the command we use for creating a mockup instance on the directory
/tmp/mockup/
is:
This command returns a list of Tezos addresses that we can use with the client in subsequent commands. As recommended in the Tezos documentation, we can add a shell alias to avoid mistakes:
We can list the addresses returned above by running:
We are now ready to originate (or "deploy") the contract on our mockup Tezos:
The --init
argument ("foo"
) is the initial storage for our
deployed contract. In case we had a more complex storage, we could
have used LIGO's compile-storage
sub-command to compile a LIGO
expression to a Michelson storage.
Now it is time to test! The property we want to check is that if we
execute Append ("bar")
on our contract with storage "foo"
, then
the contract updates its storage to "foobar"
.
As a first sanity check, we can confirm that the storage is currently "foo"
:
Then, we execute a call to our contract with parameter Append
("bar")
. To do so, we first compile the parameter as follows:
So our parameter is simply the string (notice that the constructor
Append
was removed). We execute a call to the contract with this
compiled parameter as follows:
We have chosen bootstrap2
as the origin of this call (for no
particular reason, any address could do).
We can finally check that that our property holds: the storage is now "foobar":
Good! Our contract passed the test successfully!
#
Testing LIGO codeThe LIGO command-line interpreter provides sub-commands to test directly your LIGO code. The three main sub-commands we currently support are:
interpret
test
dry-run
We will show how to use the first two, while an example on how to use the third one was already explained in the here.
interpret
#
Testing with The sub-command interpret
allows to interpret an expression in a
context initialised by a source file. The interpretation is done using
Michelson's interpreter.
Let's see how it works on an example. Suppose we write the following contract which we want to test.
This contract keeps an integer as storage, and has three entry-points:
one for incrementing the storage, one for decrementing the storage,
and one for resetting the storage to 0
.
As a simple property, we check whether starting with an storage of
10
, if we execute the entry-point for incrementing 32
, then we get
a resulting storage of 42
. For checking it, we can interpret the
main
function:
With the argument --init-file
we pass the contract we want to test,
and the sub-command requires also the expression to evaluate in that
context, in this case, a call to our contract (main
) with parameter
Increment (32)
and storage 10
. As a result, we can check that the
resulting storage is 42
(the second component of the pair), and
there are no further operations to execute (the first component).
We can tune certain parameters of the execution by passing them as arguments:
test
#
Testing with The sub-command test
can be used to test a contract using LIGO. It
differs from interpret
as in this case we can describe the test
internally using LIGO code, and no Michelson code is actually
evaluated.
⚠️ Please keep in mind that this sub-command is still BETA, and that there are features that are work in progress and are subject to change. No real test procedure should rely on this sub-command alone.
Resuming the example we used in the explanation for interpret
, let's
add a new LIGO entry that uses the extra primitives provided by test
to test the contract above. This time we will simulate that the
contract is actually deployed to an address, and check inside LIGO
that the resulting storage is 42
after executing a call to
Increment
:
Notice that now we wrote the property inside LIGO, using:
Test.originate
to deploy a contract.Test.external_call
to simulate an external call.Test.get_storage
to check the storage from a contract.
A property like testme
is a definition of a boolean value. The
sub-command test
evaluates a test, and returns whether it was
successful or not (i.e. returned true
or false
).
The extra features we can use in LIGO when using the sub-command
test
are the following:
Test.originate c st
binds contractc
with the addressaddr
which is returned,st
as the initial storage.Test.set_now t
sets the current time tot
.Test.set_balance addr b
sets the balance of contract bound to addressaddr
(returnsunit
).Test.external_call addr p amt
performs a call to contract bound toaddr
with parameterp
and amountamt
(returnsunit
).Test.get_storage addr
returns current storage bound to addressaddr
.Test.get_balance
returns current balance bound to addressaddr
.Test.assert_failure (f : unit -> _)
returnstrue
iff ()
fails.Test.log x
printsx
into the console.