Unit Testing
Sui supports the Move Testing Framework. Here, we will create some unit tests for Managed Coin
to show how to write unit tests and run them.
Testing Environment
Sui Move test code is just like any other Sui Move code, but it has special annotations and functions to distinguish it from the actual production code.
Test functions or modules start with the #[test]
or #[test_only]
annotation.
#![allow(unused)] fn main() { #[test_only] module fungible_tokens::managed_tests { #[test] fun mint_burn() { } } }
We will put the unit tests for Managed Coin
into a separate testing module called managed_tests
.
Each function inside this module can be seen as one unit test consisting of one or more transactions. We'll write one unit test called mint_burn
.
Test Scenario
Inside the testing environment, we will be mainly leveraging the test_scenario
package to simulate a runtime environment. The main object we need to understand and interact with here is the Scenario
object. A Scenario
simulates a multi-transaction sequence, and it can be initialized with the sender address as follows:
#![allow(unused)] fn main() { // Initialize a mock sender address let addr1 = @0xA; // Begins a multi-transaction scenario with addr1 as the sender let scenario = test_scenario::begin(addr1); ... // Cleans up the scenario object test_scenario::end(scenario); }
💡Note that the Scenario
object is not droppable, so it must be explicitly cleaned up at the end of its scope using test_scenario::end
.
Initializing the Module State
To test our Managed Coin
module, we need first to initialize the module state. Given that our module has an init
function, we need to first create a test_only
init function inside the managed
module:
#![allow(unused)] fn main() { #[test_only] /// Wrapper of module initializer for testing public fun test_init(ctx: &mut TxContext) { init(MANAGED {}, ctx) } }
This is essentially a mock init
function that can only be used for testing. Then we can initialize the runtime state in our scenario by simply calling this function:
#![allow(unused)] fn main() { // Run the managed coin module init function { managed::test_init(ctx(&mut scenario)) }; }
Minting
We use the next_tx
method to advance to the next transaction in our scenario where we want to mint a Coin<MANAGED>
object.
To do this, we need first to extract the TreasuryCap<MANAGED>
object. We use a special testing function called take_from_sender
to retrieve this from our scenario. Note that we need to pass into take_from_sender
the type parameter of the object we are trying to retrieve.
Then we simply call the managed::mint
using all the necessary parameters.
At the end of this transaction, we must return the TreasuryCap<MANAGED>
object to the sender address using test_scenario::return_to_address
.
#![allow(unused)] fn main() { next_tx(&mut scenario, addr1); { let treasurycap = test_scenario::take_from_sender<TreasuryCap<MANAGED>>(&scenario); managed::mint(&mut treasurycap, 100, addr1, test_scenario::ctx(&mut scenario)); test_scenario::return_to_address<TreasuryCap<MANAGED>>(addr1, treasurycap); }; }
Burning
To test burning a token, the procedure is very similar to testing minting. The only difference is that we must also retrieve a Coin<MANAGED>
object from the person it was minted to.
Running Unit Tests
The full managed_tests
module source code can be found under example_projects
folder.
To execute the unit tests, navigate to the project directory in CLI and enter the following command:
sui move test
You should see console output indicating which unit tests have passed or failed.