Hello world! Follow us for tips and insights on branding, marketing, and entrepreneurship. Join us in creating the future of Web3.

Sui Move Lecture #4 Functions and strings
In this lecture, we will talk about functions and strings in Sui Move. You can see the public functions of any packages with explorer.public functionsFunction VisibilitySui Move functions have three types of visibility:private: the default visibility of a function; it can only be accessed by functions inside the same modulepublic: the function is accessible by functions inside the same module, and by functions defined in another modulepublic(friend): the function is accessible by functions in...

Meet the Web3 Projects Building Smart Contracts on Bitcoin
For quite some time, the cryptocurrency ecosystem can be quickly summarized this way: you use Bitcoin to spend and store value, but you build apps that can increase this value on the Ethereum blockchain. But this year, several developers working on Bitcoin have launched breakthrough after breakthrough that would allow Bitcoin to also be the blockchain that you can use to build apps. While new features such as Ordinals and BRC-20 have gotten most of the spotlight, the Web3 movement that is wor...

Generative Art: The Next High-Value Web3 Frontier
The rise of Web3 and AI has unlocked new possibilities for the generative art genre, from new artworks and art theories to increased financial viability. Learn more about generative art and our role in shaping its future in this article. The intersection of art and technology has been a fascinating space to study and draw inspiration from. Each new innovation results in the unlocking of new ways to express oneself in the world. The Web3 industry is no stranger to this progression, especially ...

Sui Move Lecture #4 Functions and strings
In this lecture, we will talk about functions and strings in Sui Move. You can see the public functions of any packages with explorer.public functionsFunction VisibilitySui Move functions have three types of visibility:private: the default visibility of a function; it can only be accessed by functions inside the same modulepublic: the function is accessible by functions inside the same module, and by functions defined in another modulepublic(friend): the function is accessible by functions in...

Meet the Web3 Projects Building Smart Contracts on Bitcoin
For quite some time, the cryptocurrency ecosystem can be quickly summarized this way: you use Bitcoin to spend and store value, but you build apps that can increase this value on the Ethereum blockchain. But this year, several developers working on Bitcoin have launched breakthrough after breakthrough that would allow Bitcoin to also be the blockchain that you can use to build apps. While new features such as Ordinals and BRC-20 have gotten most of the spotlight, the Web3 movement that is wor...

Generative Art: The Next High-Value Web3 Frontier
The rise of Web3 and AI has unlocked new possibilities for the generative art genre, from new artworks and art theories to increased financial viability. Learn more about generative art and our role in shaping its future in this article. The intersection of art and technology has been a fascinating space to study and draw inspiration from. Each new innovation results in the unlocking of new ways to express oneself in the world. The Web3 industry is no stranger to this progression, especially ...
Hello world! Follow us for tips and insights on branding, marketing, and entrepreneurship. Join us in creating the future of Web3.

Subscribe to Artech.Club

Subscribe to Artech.Club
Share Dialog
Share Dialog


<100 subscribers
<100 subscribers
Hello, everyone! In today’s lecture, we will discuss the Sui Move unit test. Following our previous lectures, you should have a general idea about how to code with Sui Move.
If you didn’t read the previous lectures, check out here.
Unit tests are necessary for smart contracts because they:
Ensure correctness: By writing unit tests, developers can verify that their smart contracts perform as intended and handle various scenarios correctly.
Identify bugs and vulnerabilities: Unit tests help uncover errors, bugs, and vulnerabilities in smart contracts, allowing developers to fix them before deployment.
Facilitate maintenance and upgrades: Well-written unit tests make it easier to maintain and upgrade smart contracts over time, as they provide a safety net for detecting regressions.
Aid collaboration: Unit tests serve as documentation and examples of how smart contracts should be used, facilitating collaboration among developers and enabling others to understand contract functionality quickly.
Here is an example.
module my_pkg::add {
#[test_only] // Annotating Data Only Used in Tests
const EAddTestError: u64 = 0;
public fun add(a: u64, b: u64): u64 {
a + b
}
#[test] // Adding the "test" Label to Identify Test Cases
fun test_add() {
assert!(add(4, 5) == 9, EAddTestError);
}
}
Unit test command: sui move test --path <pkg_path>
If the command executes without any errors, it indicates that the test case has passed.
Workflow:
Write a test method: Create a method specifically for testing the functionality you want to verify. Make sure to label it with the appropriate test tag, such as @Test (depending on the testing framework you're using).
Invoke the method under test and evaluate the results: Call the method or functionality you want to test and assess whether the outcome aligns with your expectations. You can use assertions or comparisons to validate the expected behavior.
Run the test command: Execute the test command provided by your testing framework or tool. This command will run all the test methods with the designated test labels and report the results, indicating whether each test passed or failed.
Sometimes, when programming, you may encounter bugs that are difficult to locate. Let's make some modifications to the example above.
module my_pkg::add {
...
use std::debug; // import debug module
public fun add(a: u64, b: u64): u64 {
let res = a + b;
debug::print(&res); // print add result
res
}
...
}
If you run the test again, you can see the result [debug] 9
Let’s use Test Scenario to simulate user interactions.
// Copyright (c) 2022, Sui Foundation
// SPDX-License-Identifier: Apache-2.0
module counter::counter {
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
#[test_only]
use sui::test_scenario;
#[test_only]
const EAddIncrementError: u64 = 0;
/// An object that contains an arbitrary string
struct CounterObject has key, store {
id: UID,
/// A string contained in the object
value: u64
}
public entry fun create(ctx: &mut TxContext) {
let object = CounterObject {
id: object::new(ctx),
value:0
};
transfer::public_transfer(object, tx_context::sender(ctx));
}
public entry fun increment(counter: &mut CounterObject) {
counter.value = counter.value + 1;
}
public entry fun setValue(counter: &mut CounterObject, number:u64) {
counter.value = number;
}
#[test_only]
public fun init_test(ctx: &mut TxContext) {
transfer::share_object( CounterObject { id: object::new(ctx), value: 0 } )
}
#[test]
fun test_increment() {
let user = @0x0; //
let senario_val = test_scenario::begin(user);
let senario = &mut senario_val;
init_test(test_scenario::ctx(senario)); //
test_scenario::next_tx(senario, user); //
let counter = test_scenario::take_shared<CounterObject>(senario); //
assert!(counter.value == 0, 0); //
increment(&mut counter); //
assert!(counter.value == 1, 0); //
test_scenario::return_shared(counter);
test_scenario::end(senario_val);
}
}
If you run the test, you will see an error

This is because the object is not handled correctly after the test. Add the following lines to fix the problem.
#[test]
...
fun test_increment() {
...
test_scenario::return_shared(counter);
test_scenario::end(senario_val);
}
...
If we run again, the test cases will pass.

In more complex scenarios, separating the test cases into dedicated test modules or files can improve code organization and readability. Below is an example of extracting the counter test cases into a separate module called counter_test, focusing solely on the testing logic:
// Copyright (c) 2022, Sui Foundation
// SPDX-License-Identifier: Apache-2.0
module counter::counter {
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
/// An object that contains an arbitrary string
struct CounterObject has key, store {
id: UID,
/// A string contained in the object
value: u64
}
public entry fun create(ctx: &mut TxContext) {
let object = CounterObject {
id: object::new(ctx),
value:0
};
transfer::public_transfer(object, tx_context::sender(ctx));
}
public entry fun increment(counter: &mut CounterObject) {
counter.value = counter.value + 1;
}
public entry fun setValue(counter: &mut CounterObject, number:u64) {
counter.value = number;
}
#[test_only]
public fun init_test(ctx: &mut TxContext) {
transfer::share_object( CounterObject { id: object::new(ctx), value: 0 } )
}
}
Create a new `counter_test` module
#[test_only]
module my_pkg::counter_test {
use sui::test_scenario;
use my_pkg::counter::{Self, Counter};
const EAddIncrementError: u64 = 0;
#[test]
fun test_increment() {
let user = @0x0;
let senario_val = test_scenario::begin(user);
let senario = &mut senario_val;
counter::init_test(test_scenario::ctx(senario));
test_scenario::next_tx(senario, user);
let counter = test_scenario::take_shared<Counter>(senario);
assert!(counter::value(&counter) == 0, EAddIncrementError);
counter::increment(&mut counter);
assert!(counter::value(&counter) == 1, EAddIncrementError);
test_scenario::return_shared(counter);
test_scenario::end(senario_val);
}
}
By separating testing logic from business logic, you can maintain code clarity, improve readability, and make it easier to maintain and update your tests as your project evolves.
This tutorial analyzes the fundamental testing process and common advanced testing concepts that can be applied to small to medium-scale Sui Move contract development. Based on our development experience, as the complexity of the application increases, testing tasks become more challenging. Therefore, it is essential to enhance the reusability of testing logic by:
Encapsulating Generic Testing Logic: For instance, referring to web2 testing and utilizing existing testing frameworks.
Reusing Business-Level Testing Logic: Many testing scenarios, setups, and teardowns can be reused.
Hello, everyone! In today’s lecture, we will discuss the Sui Move unit test. Following our previous lectures, you should have a general idea about how to code with Sui Move.
If you didn’t read the previous lectures, check out here.
Unit tests are necessary for smart contracts because they:
Ensure correctness: By writing unit tests, developers can verify that their smart contracts perform as intended and handle various scenarios correctly.
Identify bugs and vulnerabilities: Unit tests help uncover errors, bugs, and vulnerabilities in smart contracts, allowing developers to fix them before deployment.
Facilitate maintenance and upgrades: Well-written unit tests make it easier to maintain and upgrade smart contracts over time, as they provide a safety net for detecting regressions.
Aid collaboration: Unit tests serve as documentation and examples of how smart contracts should be used, facilitating collaboration among developers and enabling others to understand contract functionality quickly.
Here is an example.
module my_pkg::add {
#[test_only] // Annotating Data Only Used in Tests
const EAddTestError: u64 = 0;
public fun add(a: u64, b: u64): u64 {
a + b
}
#[test] // Adding the "test" Label to Identify Test Cases
fun test_add() {
assert!(add(4, 5) == 9, EAddTestError);
}
}
Unit test command: sui move test --path <pkg_path>
If the command executes without any errors, it indicates that the test case has passed.
Workflow:
Write a test method: Create a method specifically for testing the functionality you want to verify. Make sure to label it with the appropriate test tag, such as @Test (depending on the testing framework you're using).
Invoke the method under test and evaluate the results: Call the method or functionality you want to test and assess whether the outcome aligns with your expectations. You can use assertions or comparisons to validate the expected behavior.
Run the test command: Execute the test command provided by your testing framework or tool. This command will run all the test methods with the designated test labels and report the results, indicating whether each test passed or failed.
Sometimes, when programming, you may encounter bugs that are difficult to locate. Let's make some modifications to the example above.
module my_pkg::add {
...
use std::debug; // import debug module
public fun add(a: u64, b: u64): u64 {
let res = a + b;
debug::print(&res); // print add result
res
}
...
}
If you run the test again, you can see the result [debug] 9
Let’s use Test Scenario to simulate user interactions.
// Copyright (c) 2022, Sui Foundation
// SPDX-License-Identifier: Apache-2.0
module counter::counter {
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
#[test_only]
use sui::test_scenario;
#[test_only]
const EAddIncrementError: u64 = 0;
/// An object that contains an arbitrary string
struct CounterObject has key, store {
id: UID,
/// A string contained in the object
value: u64
}
public entry fun create(ctx: &mut TxContext) {
let object = CounterObject {
id: object::new(ctx),
value:0
};
transfer::public_transfer(object, tx_context::sender(ctx));
}
public entry fun increment(counter: &mut CounterObject) {
counter.value = counter.value + 1;
}
public entry fun setValue(counter: &mut CounterObject, number:u64) {
counter.value = number;
}
#[test_only]
public fun init_test(ctx: &mut TxContext) {
transfer::share_object( CounterObject { id: object::new(ctx), value: 0 } )
}
#[test]
fun test_increment() {
let user = @0x0; //
let senario_val = test_scenario::begin(user);
let senario = &mut senario_val;
init_test(test_scenario::ctx(senario)); //
test_scenario::next_tx(senario, user); //
let counter = test_scenario::take_shared<CounterObject>(senario); //
assert!(counter.value == 0, 0); //
increment(&mut counter); //
assert!(counter.value == 1, 0); //
test_scenario::return_shared(counter);
test_scenario::end(senario_val);
}
}
If you run the test, you will see an error

This is because the object is not handled correctly after the test. Add the following lines to fix the problem.
#[test]
...
fun test_increment() {
...
test_scenario::return_shared(counter);
test_scenario::end(senario_val);
}
...
If we run again, the test cases will pass.

In more complex scenarios, separating the test cases into dedicated test modules or files can improve code organization and readability. Below is an example of extracting the counter test cases into a separate module called counter_test, focusing solely on the testing logic:
// Copyright (c) 2022, Sui Foundation
// SPDX-License-Identifier: Apache-2.0
module counter::counter {
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
/// An object that contains an arbitrary string
struct CounterObject has key, store {
id: UID,
/// A string contained in the object
value: u64
}
public entry fun create(ctx: &mut TxContext) {
let object = CounterObject {
id: object::new(ctx),
value:0
};
transfer::public_transfer(object, tx_context::sender(ctx));
}
public entry fun increment(counter: &mut CounterObject) {
counter.value = counter.value + 1;
}
public entry fun setValue(counter: &mut CounterObject, number:u64) {
counter.value = number;
}
#[test_only]
public fun init_test(ctx: &mut TxContext) {
transfer::share_object( CounterObject { id: object::new(ctx), value: 0 } )
}
}
Create a new `counter_test` module
#[test_only]
module my_pkg::counter_test {
use sui::test_scenario;
use my_pkg::counter::{Self, Counter};
const EAddIncrementError: u64 = 0;
#[test]
fun test_increment() {
let user = @0x0;
let senario_val = test_scenario::begin(user);
let senario = &mut senario_val;
counter::init_test(test_scenario::ctx(senario));
test_scenario::next_tx(senario, user);
let counter = test_scenario::take_shared<Counter>(senario);
assert!(counter::value(&counter) == 0, EAddIncrementError);
counter::increment(&mut counter);
assert!(counter::value(&counter) == 1, EAddIncrementError);
test_scenario::return_shared(counter);
test_scenario::end(senario_val);
}
}
By separating testing logic from business logic, you can maintain code clarity, improve readability, and make it easier to maintain and update your tests as your project evolves.
This tutorial analyzes the fundamental testing process and common advanced testing concepts that can be applied to small to medium-scale Sui Move contract development. Based on our development experience, as the complexity of the application increases, testing tasks become more challenging. Therefore, it is essential to enhance the reusability of testing logic by:
Encapsulating Generic Testing Logic: For instance, referring to web2 testing and utilizing existing testing frameworks.
Reusing Business-Level Testing Logic: Many testing scenarios, setups, and teardowns can be reused.
No activity yet