Test Fail Cases

Tests should also cover the failure cases. Move provides a special attribute to mark tests that are expected to fail. The #[expected_failure] attribute can be used to mark a test that is expected to fail. It can be used with or without an abort code. If an abort code is provided, the test will fail if it does not abort with the provided code. If no abort code is provided, the test will fail if it does not abort.

module examples::expected_failure {
    use std::option::{Self, Option};

    /// Trying to take a value out of an empty container.
    const EContainerEmpty: u64 = 0;
    /// Trying to put a value into a full container.
    const EContainerFull: u64 = 1;
    /// Function is not implemented.
    const ENotImplemented: u64 = 3;

    /// A container that can hold a value of type T or nothing.
    struct Container<T> has copy, store, drop {
        inner: Option<T>
    }

    /// Creates a new container with the given value.
    public fun new<T>(inner: T): Container<T> {
        Container { inner: option::some(inner) }
    }

    /// Creates a new empty container.
    public fun empty<T>(): Container<T> {
        Container { inner: option::none() }
    }

    /// Takes the value out of the container. Aborts if the container is empty.
    public fun take<T>(container: &mut Container<T>): T {
        assert!(option::is_some(&container.inner), EContainerEmpty);
        option::extract(&mut container.inner)
    }

    /// Put a value into the container. Aborts if the container is full.
    public fun put<T>(container: &mut Container<T>, value: T) {
        assert!(option::is_none(&container.inner), EContainerFull);
        option::fill(&mut container.inner, value);
    }

    #[test]
    // An example of a regular test function. Not expected to fail.
    fun create_empty() {
        let container = empty<u64>();
        assert!(option::is_none(&container.inner), 0);
    }

    #[test]
    #[expected_failure]
    // The "expected_failure" attribute can be added to a test function. And
    // the test will pass if it the function fails with any abort code.
    fun showcase_expected_failure_fail() {
        abort ENotImplemented
    }

    #[test]
    #[expected_failure(abort_code = EContainerEmpty)]
    // It is considered a good practice to specify the expected abort code. It
    // is especially important when a function has multiple abort points.
    //
    // The abort code is a constant that is used to identify the expected
    // code. It can be both a value and a name of a constant. It also allows
    // importing constants from other modules!
    fun try_take_from_empty_container_fail() {
        let container = empty<u64>();
        let _ = take(&mut container);
    }

    #[test, expected_failure(abort_code = EContainerFull)]
    // Attributes can be combined into a single attribute list, separated by
    // commas. Purely a style choice.
    fun try_put_into_full_container_fail() {
        let container = new(42u64); // create a container with a value
        put(&mut container, 42); // try to put another value into it
    }
}