Writing Tests#

Tests are important for improving quality and maintainability of a program. They verify the behavior of a program and also serves as a specification to avoid regressions over time.

MoonBit comes with test support to make the writing easier and simpler.

Test Blocks#

MoonBit provides the test code block for writing inline test cases. For example:

test "test_name" {
  assert_eq!(1 + 1, 2)
  assert_eq!(2 + 2, 4)
  inspect!([1, 2, 3], content="[1, 2, 3]")
}

A test code block is essentially a function that returns a Unit but may throws a String on error, or Unit!String as one would see in its signature at the position of return type. It is called during the execution of moon test and outputs a test report through the build system. The assert_eq function is from the standard library; if the assertion fails, it prints an error message and terminates the test. The string "test_name" is used to identify the test case and is optional.

If a test name starts with "panic", it indicates that the expected behavior of the test is to trigger a panic, and the test will only pass if the panic is triggered. For example:

test "panic_test" {
  let _ : Int = Option::None.unwrap()

}

Snapshot Tests#

Writing tests can be tedious when specifying the expected values. Thus, MoonBit provides three kinds of snapshot tests. All of which can be inserted or updated automatically using moon test --update.

Snapshotting Show#

We can use inspect!(x, content="x") to inspect anything that implements Show trait. As we mentioned before, Show is a builtin trait that can be derived, providing to_string that will print the content of the data structures. The labelled argument content can be omitted as moon test --update will insert it for you:

struct X { x : Int } derive(Show)

test "show snapshot test" {
  inspect!({x: 10}, content="{x: 10}")
}

Snapshotting JSON#

The problem with the derived Show trait is that it does not perform pretty printing, resulting in extremely long output.

The solution is to use @json.inspect!(x, content=x). The benefit is that the resulting content is a JSON structure, which can be more readable after being formatted.

enum Rec {
  End
  Really_long_name_that_is_difficult_to_read(Rec)
} derive(Show, ToJson)

test "json snapshot test" {
  let r = Really_long_name_that_is_difficult_to_read(
    Really_long_name_that_is_difficult_to_read(
      Really_long_name_that_is_difficult_to_read(End),
    ),
  )
  inspect!(
    r,
    content="Really_long_name_that_is_difficult_to_read(Really_long_name_that_is_difficult_to_read(Really_long_name_that_is_difficult_to_read(End)))",
  )
  @json.inspect!(
    r,
    content={
      "$tag": "Really_long_name_that_is_difficult_to_read",
      "0": {
        "$tag": "Really_long_name_that_is_difficult_to_read",
        "0": {
          "$tag": "Really_long_name_that_is_difficult_to_read",
          "0": { "$tag": "End" },
        },
      },
    },
  )
}

One can also implement a custom ToJson to keep only the essential information.

Snapshotting Anything#

Still, sometimes we want to not only record one data structure but the output of a whole process.

A full snapshot test can be used to record anything using @test.T::write and @test.T::writeln:

test "record anything" (t : @test.T) {
  t.write("Hello, world!")
  t.writeln(" And hello, MoonBit!")
  t.snapshot!(filename="record_anything.txt")
}

This will create a file under __snapshot__ of that package with the given filename:

Hello, world! And hello, MoonBit!

This can also be used for applications to test the generated output, whether it were creating an image, a video or some custom data.

BlackBox Tests and WhiteBox Tests#

When developing libraries, it is important to verify if the user can use it correctly. For example, one may forget to make a type or a function public. That’s why MoonBit provides BlackBox tests, allowing developers to have a grasp of how others are feeling.

  • A test that has access to all the members in a package is called a WhiteBox tests as we can see everything. Such tests can be defined inline or defined in a file whose name ends with _wbtest.mbt.

  • A test that has access only to the public members in a package is called a BlackBox tests. Such tests need to be defined in a file whose name ends with _test.mbt.

The WhiteBox test files (_wbtest.mbt) imports the packages defined in the import and wbtest-import sections of the package configuration (moon.pkg.json).

The BlackBox test files (_test.mbt) imports the current package and the packages defined in the import and test-import sections of the package configuration (moon.pkg.json).