Guide for Move Transactional Testing
The Move transactional testing feature described in this document is in exploratory phase. Support for it will depend on its usage and adoption by the Aptos community.
If you are a smart contract developer using the Move language, then you can use the Move transactional tests to write and run end-to-end tests.
This tutorial walks you through the steps for writing and running end-to-end Move transactional tests using the Aptos CLI.
Compared to the Move unit tests, which are useful for verifying the intra-module code correctness, the Move transactional tests enable you to test a broader spectrum of use cases, such as publishing the Move modules and the inter-module interactions.
Overview
See this aptos_test_harness
GitHub folder for how Move transactional tests look like.
A Move transactional test suite consists of two types of files:
- The Move files, with
*.move
extension. These Move files contain the transactional tests. Transactional tests are Rust clap (Command Line Argument Parser ) commands. They are written as comments in the Move files. To distinguish from the normal Move comments, these transactional test commands are prefixed with a special comment string indicator//#
. - The baseline files, with
*.exp
extension. These baseline files will be created empty when you run theaptos
CLI for the first time. If you run the test withUB=1
option the first time, the baseline files will be populated with the test output.
Quickstart
Before you get into the details, you can follow the below steps to run a sample Move transactional test and see the results.
Step 1: Install Aptos CLI
Make sure you have installed aptos
, the Aptos CLI tool. See Aptos CLI for how to install and use the aptos
CLI tool.
Step 2: Run the Move transactional test suite
- Clone or download the
aptos-core
GitHub repo. - The Move transactional test suite is located in
aptos-core/aptos-move/aptos-transactional-test-harness/test
. - Run the
aptos
CLI command with the optionmove transactional-test
.Use UB=1When you run the
aptos
CLI command make sure to include theUB=1
only during the first time or if you have updated the tests, as shown below.
UB=1 aptos move transactional-test --root-path aptos-core/aptos-move/aptos-transactional-test-harness/test
- The
--root-path
specifies where all the tests are located. - The test runner walks down the directory hierarchies and finds the tests, specified as comments with the special prefix
//#
, in the files whose names end with.move
or.mvir
. - The Move transactional test runner runs the tests and compares the output of the tests with the baseline files. Baseline files hold the contents of the results that were generated during the first run.
Examples
This section presents examples showing how to write and run various Move transactional test commands.
Create accounts
//# create_account —name Alice [--initial-coins 10000]
The create_account
command generates a deterministic private key, public key pair and creates a named account address (Alice in the above example).
Initial coins can be specified, otherwise, a default value of 10000
is used.
Publish modules
//# publish [--gas-budget 100]
module Alice::first_module {
public entry fun foo() {
return
}
}
The publish
command publishes a Move module to a designated account (Alice in the above example). Optionally, the number of gas units allowed for publishing the transaction can be specified via --gas-budget
.
The default value is the maximum coins available at the sender account.
Run module script functions
//# run --signers Alice [--args x"68656C6C6F20776F726C64"] [--type-args "0x1::aptos_coin::AptosCoin"] [--expiration 1658432810] [--sequence-number 1] [--gas-price 1] [--show-events] -- Alice::first_module::function_name
The run
command runs a module script function by sending a transaction.
In the above example:
--signers
specify who signs and sends the transaction.Alice::first_module::function_name
is the fully qualified Move module function name.--args
specify the arguments to pass to the script function.--type-args
specify the type arguments if the script function is a generic function.--expiration
transaction expiration time.--sequence-number
account sequence number.--gas-price
gas unit price.--show-events
print out the transaction events if specified.
Execute scripts
//# run --script --signers Alice [--args x"68656C6C6F20776F726C64"] [--type-args "0x1::aptos_coin::AptosCoin"] [--expiration 1658432810] [--sequence-number 1] [--gas-price 1]
script {
use aptos_framework::coin;
use aptos_framework::aptos_coin::AptosCoin;
fun main(sender: &signer, receiver: address, amount: u64) {
coin::transfer<AptosCoin>(sender, receiver, amount);
}
}
The run
command can also be used with the --script
option to execute a script.
View resources
//# view_resource --address Alice --type 0x1::coin::CoinStore<0x1::test_coin::TestCoin> [--field coin.value]
The view_resource
prints out the resources contained at an address.
--address
of the account whose resources should be printed.--type
the type of resource to be printed.--field
prints out only the specified field.
View tables
//# view_table --table_handle 5713946181763753045826830927579154558 --key_type 0x1::string::String --key_value x"68656C6C6F20776F726C64" --value_type 0x1::token::Collection
The view_table
prints out an item in a table by the item’s key. For example, in the below Move code:
struct Collections has key {
collections: Table<string::String, Collection>,
}
- In storage, the table
collections
has a handle stored in resourceCollections
. - To query a table item by key, we need to know the table handle information
--table_handle 5713946181763753045826830927579154558
. - The
table_handle
is handle of the table to be queried. It can be looked up by the commandview_resource
. - The
--key_type
is the type info for the key. To get the value for a key, use theview_table
option with--key_type
and--key_value
. - The
--key_value
is the key that is used to retrieve the value. - The
key_type
is used to deserialize thekey_value
to obtain a raw key, which can be used to query storage. - The
--value_type
the type information for deserializing the table value. The storage return value also must be deserialized to be useful. Therefore,--value_type
is also needed.