Sitemap
The Sui Stack

This collaborative publication is your go-to resource for building on Sui, the most advanced Web3 stack. DM @BL0CKRUNNER on Twitter to add your article to this publication!

Code in Move [7] — Unit Testing on Sui

5 min readMay 6, 2024

--

Press enter or click to view image in full size

As you should know, comprehensive unit testing is essential before shipping code to production. In this article we’ll explore how to efficiently test packages on Sui. The docs are very light, and if you follow their example you will end up with insanely long test modules. Let’s see how I personally structure my tests to keep them as concise and efficient as possible.

The helper modules for testing can be found here, and each module can have its own function for testing such as coin::mint_for_testing.

Project

Structure

It is recommended to place your test files under a tests/ directory in your project (not in sources/). Since your test will be separate modules, private functions and structs fields won’t be directly accessible.

In each module where you need to call private functions or read some struct fields, you can write public functions with the #[test_only] annotation. Modules, imports, constants and functions annotated like this will therefore be excluded from the build process. You will always need to do so for init functions and others.

Press enter or click to view image in full size

Let’s now take a look at the package manifest: Move.toml

Manifest

The [dependencies] section is used to specify the dependencies of the project. The dependency specification can be a git repository URL or a path to the local directory. It is possible to add [dev-dependencies] section to the manifest. It is used to override dependencies in the dev and test modes.

# git repository
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }

# local directory
MyPackage = { local = "../my-package" }

The [addresses] section is used to add aliases for the addresses. For example, if you add alice = "0xA11CE" to this section, you can use alice instead of @0xA11CE in the code. The [dev-addresses] section is the same as [addresses], but only works for the test and dev modes. Important to note that it is impossible to introduce new aliases in this section, only override the existing ones.

Tests

Principles

In general, each module will have a corresponding testing module. It should be named module_name_tests.move. In this module we can import std::debug::print that will likely be very useful. By passing a reference to a value, it prints it to the terminal.

Then I usually define a few addresses that will be the users in the tests. I also create a hot potato that will hold all the state of the mocked environment, including the blockchain (Scenario and Clock), the package (Pond and DuckManager) and its dependencies (the rest).

Here is how it works. The idea is to define a function that will take care of initializing all the objects and modules we need, and assemble all the data into one World struct. We can then easily use this function at the beginning of each test.

Press enter or click to view image in full size

Since World doesn’t have the drop ability, we need another function to return all objects and destroy the hot potato.

Press enter or click to view image in full size

Then, you probably want to write helpers to chain your composable functions into one, replicating the behavior of PTBs. You can see examples here.

An important thing to know is that as long as you didn’t call world.scenario.next_tx(USER), object creations and transfers are not taken into account in the Scenario. In other words, in order to access an object with one of the various “take” functions the scenario must first be advanced to a new transaction via next_tx. This is not an issue if you only modify your objects and don’t transfer it until the end of the scenario, which often the case for short unit tests.

To understand better how it works, I strongly recommend you to go over the modules for testing.

Writing and Running Tests

Finally you can write your tests. They should start by your start_world function, returning the World that you can pass with its wrapped objects into your functions, and end with end_world to return and destroy what it is needed.

Tests should have the #[test] annotation to be executed by the testing framework. You can add #[expected_failure(abort_code=your_package::module::EErrorName)] if your test is supposed to abort with an error.

When you’re ready run sui move test in your terminal to generate the output. You can also run sui move test <PATTERN> to run only the tests with <PATTERN> in their name.

Coverage

If you’re serious you likely want to get 100% coverage of your code with the unit tests. The Move CLI finally enables this functionality in stable mode so let’s see how to generate a report.

Start by running sui move test --coverage to generate the map.

  • sui move coverage summary shows a recap of the proportion of code tested per module
Press enter or click to view image in full size
  • sui move coverage summary --summarize-functions shows a recap of the proportion of code tested per function per module
  • sui move coverage source --module <module_name> shows exactly what part of the code is untested per module
Press enter or click to view image in full size
  • finally if you’re a move warrior, sui move coverage bytecode --module <module_name> shows exactly what part of the bytecode is untested per module
Press enter or click to view image in full size

Here are the most important tricks and principles I experimented with during my time writing code on Sui. I believe this is one of the most efficient ways to write unit tests and it saves me a lot of time since I do like that. Feel free to dm on Twitter if you have any questions.

--

--

The Sui Stack
The Sui Stack

Published in The Sui Stack

This collaborative publication is your go-to resource for building on Sui, the most advanced Web3 stack. DM @BL0CKRUNNER on Twitter to add your article to this publication!

Thouny
Thouny

Written by Thouny

A seeker of truths in the realms of (broken) technology, society, and nature. Follow a blockchain engineer trying to broaden horizons.

No responses yet