Unit testing

Tests are Cairo functions that verify that the non-test code is functioning in the expected manner. The bodies of test functions typically perform some setup, run the code we want to test, then assert whether the results are what we expect.

Most unit tests go into a tests mod with the #[cfg(test)] attribute. Test functions are marked with the #[test] attribute.

Tests fail when something in the test function panics. There are some helper macros:

  • assert!(expression) - panics if expression evaluates to false.
  • assert_eq!(left, right) and assert_ne!(left, right) - testing left and right expressions for equality.
  • assert_lt!(left, right) and assert_gt!(left, right) - testing left and right expressions for less than and greater than respectively.
  • assert_le!(left, right) and assert_ge!(left, right) - testing left and right expressions for less than or equal to and greater than or equal to respectively.
// Basic add example
fn add(a: u32, b: u32) -> u32 {
    a + b
}

// This is a really bad adding function, its purpose is to fail in this
// example.
fn bad_add(a: u32, b: u32) -> u32 {
    a - b
}

#[cfg(test)]
mod add_tests {
    // Note this useful idiom: importing names from outer (for mod tests) scope.
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(1, 2), 3);
    }

    #[test]
    fn test_bad_add() {
        // This assert would fire and test will fail.
        // Please note, that private functions can be tested too!
        assert_eq!(bad_add(1, 2), 3);
    }
}

Tests can be run with scarb test. To run specific tests, one may specify the test name to scarb test command. To run multiple tests one may specify part of a test name that matches all the tests that should be run. Here, we run all the tests in the add_tests module.

$ scarb test add
     Running test unit_testing (snforge test)
    Blocking waiting for file lock on registry db cache
   Compiling test(listings/testing/unit_testing/Scarb.toml)
    Finished `dev` profile target(s) in 6 seconds


Collected 4 test(s) from unit_testing package
Running 4 test(s) from src/
[PASS] unit_testing::add_tests::test_add (gas: ~1)
[PASS] unit_testing::ignore_tests::test_add (gas: ~1)
[FAIL] unit_testing::add_tests::test_bad_add

Failure data:
    0x7533325f737562204f766572666c6f77 ('u32_sub Overflow')

[PASS] unit_testing::ignore_tests::test_add_hundred (gas: ~1)
Tests: 3 passed, 1 failed, 0 skipped, 0 ignored, 4 filtered out

Failures:
    unit_testing::add_tests::test_bad_add

Testing panics

To check functions that should panic under certain circumstances, use attribute #[should_panic]. This attribute accepts optional parameter expected: with the text of the panic message. If your function can panic in multiple ways, it helps make sure your test is testing the correct panic.

fn divide_non_zero_result(a: u32, b: u32) -> u32 {
    if b == 0 {
        panic!("Divide-by-zero error")
    } else if a < b {
        panic!("Divide result is zero")
    }
    a / b
}

#[cfg(test)]
mod divide_tests {
    use super::*;

    #[test]
    fn test_divide() {
        assert_eq!(divide_non_zero_result(10, 2), 5);
    }

    #[test]
    #[should_panic]
    fn test_any_panic() {
        divide_non_zero_result(1, 0);
    }

    #[test]
    #[should_panic(expected: "Divide result is zero")]
    fn test_specific_panic() {
        divide_non_zero_result(1, 10);
    }
}

Running these tests gives us:

$ scarb test divide
     Running test unit_testing (snforge test)
   Compiling test(listings/testing/unit_testing/Scarb.toml)
    Finished `dev` profile target(s) in 6 seconds


Collected 3 test(s) from unit_testing package
Running 3 test(s) from src/
[PASS] unit_testing::divide_tests::test_divide (gas: ~1)
[PASS] unit_testing::divide_tests::test_any_panic (gas: ~1)

Success data:
    "Divide-by-zero error"

[PASS] unit_testing::divide_tests::test_specific_panic (gas: ~1)
Tests: 3 passed, 0 failed, 0 skipped, 0 ignored, 5 filtered out

Ignoring tests

Tests can be marked with the #[ignore] attribute to exclude some tests. Ignored tests can be run with command scarb test -- --ignored

fn add_two(a: u32, b: u32) -> u32 {
    a + b
}

#[cfg(test)]
mod ignore_tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add_two(2, 2), 4);
    }

    #[test]
    fn test_add_hundred() {
        assert_eq!(add_two(100, 2), 102);
        assert_eq!(add_two(2, 100), 102);
    }

    #[test]
    #[ignore]
    fn ignored_test() {
        assert_eq!(add_two(0, 0), 0);
    }
}
$ scarb test -- --ignored
     Running test unit_testing (snforge test)
   Compiling test(listings/testing/unit_testing/Scarb.toml)
    Finished `dev` profile target(s) in 5 seconds


Collected 1 test(s) from unit_testing package
Running 1 test(s) from src/
[PASS] unit_testing::ignore_tests::ignored_test (gas: ~1)
Tests: 1 passed, 0 failed, 0 skipped, 0 ignored, 7 filtered out