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
Expectfrom string literal: - expect_
file - Creates an instance of
ExpectFilefrom relative or absolute path:
Structs§
- Expect
- Self-updating string literal.
- Expect
File - Self-updating file.
- Position
- Position of original
expect!in the source file.