Skip to main content

Crate midenc_expect_test

Crate midenc_expect_test 

Source
Expand description

Minimalistic snapshot testing for Rust.

§Introduction

midenc_expect_test is a small addition over plain assert_eq! testing approach, which allows to automatically update tests results.

The core of the library is the expect! macro. It can be though of as a super-charged string literal, which can update itself.

Let’s see an example:

use midenc_expect_test::expect;

let actual = 2 + 2;
let expected = expect!["5"]; // or expect![["5"]]
expected.assert_eq(&actual.to_string())

Running this code will produce a test failure, as "5" is indeed not equal to "4". Running the test with UPDATE_EXPECT=1 env variable however would “magically” update the code to:

let actual = 2 + 2;
let expected = expect!["4"];
expected.assert_eq(&actual.to_string())

This becomes very useful when you have a lot of tests with verbose and potentially changing expected output.

Under the hood, the expect! macro uses file!, line! and column! to record source position at compile time. At runtime, this position is used to patch the file in-place, if UPDATE_EXPECT is set.

§Guide

expect! returns an instance of Expect struct, which holds position information and a string literal. Use Expect::assert_eq for string comparison. Use Expect::assert_debug_eq for verbose debug comparison. Note that leading indentation is automatically removed.

use midenc_expect_test::expect;

#[derive(Debug)]
struct Foo {
    value: i32,
}

let actual = Foo { value: 92 };
let expected = expect![["
    Foo {
        value: 92,
    }
"]];
expected.assert_debug_eq(&actual);

Be careful with assert_debug_eq - in general, stability of the debug representation is not guaranteed. However, even if it changes, you can quickly update all the tests by running the test suite with UPDATE_EXPECT environmental variable set.

If the expected data is too verbose to include inline, you can store it in an external file using the expect_file! macro:

use midenc_expect_test::expect_file;

let actual = 42;
let expected = expect_file!["./the-answer.txt"];
expected.assert_eq(&actual.to_string());

File path is relative to the current file.

§Suggested Workflows

I like to use data-driven tests with midenc_expect_test. I usually define a single driver function check and then call it from individual tests:

use midenc_expect_test::{expect, Expect};

fn check(actual: i32, expect: Expect) {
    let actual = actual.to_string();
    expect.assert_eq(&actual);
}

#[test]
fn test_addition() {
    check(90 + 2, expect![["92"]]);
}

#[test]
fn test_multiplication() {
    check(46 * 2, expect![["92"]]);
}

Each test’s body is a single call to check. All the variation in tests comes from the input data.

When writing a new test, I usually copy-paste an old one, leave the expect blank and use UPDATE_EXPECT to fill the value for me:

#[test]
fn test_division() {
    check(92 / 2, expect![[""]])
}

See https://blog.janestreet.com/using-ascii-waveforms-to-test-hardware-designs/ for a cool example of snapshot testing in the wild!

Macros§

expect
Creates an instance of Expect from string literal:
expect_file
Creates an instance of ExpectFile from relative or absolute path:

Structs§

Expect
Self-updating string literal.
ExpectFile
Self-updating file.
Position
Position of original expect! in the source file.