Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Testing

Testing is a crucial aspect of software development, especially in blockchain applications where security and correctness are paramount. In this section, we will cover the fundamentals of testing in Move, including how to write and organize tests effectively.

The #[test] Attribute

Tests in Move are functions marked with the #[test] attribute. This attribute tells the compiler that the function is a test function and should be run when tests are executed. Test functions are regular functions, but they must take no arguments and have no return value. They are excluded from the release compiled code and are never published.

module book::testing;

#[test_only]
use std::unit_test::assert_eq;

// The test attribute is placed before the `fun` keyword (can be both above or
// right before the `fun` keyword, as in `#[test] fun my_test() { ... }`)
// The name of the test in this case would be `book::testing::simple_test`.
#[test]
fun simple_test() {
    let sum = 2 + 2;
    assert_eq!(sum, 4);
}

// The name of this test would be `book::testing::more_advanced_test`.
#[test] fun more_advanced_test() {
    let sum = 2 + 2 + 2;
    assert_eq!(sum, 4);
}

Running Tests

To run tests, you can use the move-stylus test command from the move-stylus cli. This command will first build the package in test mode and then run all tests found in the package. In test mode, modules from both sources/ and tests/ directories are processed and their tests executed.

x@y-MacBook-Pro example % move-stylus test
COMPILING another_mod
COMPILING other_mod
COMPILING hello_world

Running 0x0::hello_world tests (./sources/hello_world.move)

  0x0::hello_world::test_1 ... PASSED
  0x0::hello_world::test_2 ... PASSED
  0x0::hello_world::test_3 ... PASSED
  0x0::hello_world::test_4 ... PASSED
  0x0::hello_world::test_5 ... PASSED
  0x0::hello_world::test_6 [expected failure] ... PASSED

Total Tests : 6, Passed: 6, Failed: 0.

Test Fail Cases with #[expected_failure]

Tests for fail cases can be marked with #[expected_failure]. This attribute, when added to a #[test] function, tells the compiler that the test is expected to fail. This is useful when you want to test that a function fails when a certain condition is met.

Note

This attribute can only be added to a #[test] function.

If execution does not result in an abort, the test will fail.

module book::testing_failure;

const EInvalidArgument: u64 = 1;

#[test]
#[expected_failure]
fun test_fail() {
    abort 0 // aborts with code 0
}

// attributes can be grouped together
#[test, expected_failure]
fun test_fail_1() {
    abort EInvalidArgument // aborts with code EInvalidArgument
}

Utilities with #[test_only]

In some cases, it is helpful to give the test environment access to some internal functions or features. This simplifies the testing process and allows for more thorough testing. However, it is important to remember that these functions should not be included in the final package. This is where the #[test_only] attribute comes in handy.

module book::testing;

#[test_only]
use std::unit_test::assert_eq;

// Public function which uses the `secret` function.
public fun multiply_by_secret(x: u64): u64 {
    x * secret()
}

/// Private function which is not available to the public.
fun secret(): u64 { 100 }

#[test_only]
/// This function is only available for testing purposes in tests and other
/// test-only functions. Mind the visibility - for `#[test_only]` it is
/// common to use `public` visibility.
public fun secret_for_testing(): u64 {
    secret()
}

#[test]
// In the test environment we have access to the `secret_for_testing` function.
fun test_multiply_by_secret() {
    let expected = secret_for_testing() * 2;
    assert_eq!(multiply_by_secret(2), expected);
}

Functions marked with the #[test_only] will be available to the test environment, and to the other modules if their visibility is set to public.