3 releases (breaking)

0.3.0 Nov 17, 2023
0.2.0 Jun 15, 2023
0.1.0 Apr 4, 2023

#258 in Testing


Used in ndjson-stream

MIT/Apache

500KB
10K SLoC

Kernal

Kernal (Kernal Extensive Rust Natural Assertion Language) allows you to use fluent assertions in Rust tests. That is, instead of writing assert_eq!(my_vec.len(), 10), you can write assert_that!(my_vec).has_length(10), making your tests more readable and enabling the framework to provide more expressive error messages. Kernal aims to provide specialized assertions for as many commonly tested properties as possible.

Writing an assertion

To write an assertion over a value, start with assert_that!(<your value>), which gives you an instance on which you can call associated functions to make your assertions. Import the specialized extension traits, such as StringAssertions for special assertions for Strings, from the prelude module. This gives you all imports you need to write every assertion supported by Kernal.

use kernal::prelude::*;

assert_that!("hello world").contains("world");

Chaining

Every assertion returns the same asserter instance to continue writing assertions on the same value. In addition, some extension traits define mapping methods that manipulate the data in some way and return asserter instances on the new data.

assert_that!("almost")
    .has_char_length(6)
    .ends_with("most")
    .to_chars()
    .is_sorted_in_strictly_ascending_order();

Creating custom assertions

Kernal allows the creation of custom assertions to test instances of your types in a more natural way. This is achieved by grouping assertions you want to offer for specific types into traits, which are then implemented on the output type of the assert_that macro. For more details, view the crate-level documentation. The example below demonstrates this process.

// Our type for which we want to write assertions.
struct Vector2f32 { x: f32, y: f32 }

// The custom assertion trait we will later implement on `AssertThat`.
trait Vector2f32Assertions {
    // The custom assertion we want to supply. It is recommended to take an owned `self` and
    // return the same instance to support chaining.
    fn has_euclidean_norm(self, expected_norm: f32, epsilon: f32) -> AssertThat<Vector2f32>;
}

impl Vector2f32Assertions for AssertThat<Vector2f32> {
    fn has_euclidean_norm(self, expected_norm: f32, epsilon: f32) -> AssertThat<Vector2f32> {
        // We get our data with `self.data()`, supplied by `AssertThatData`
        let vector = self.data();
        let actual_norm = (vector.x * vector.x + vector.y * vector.y).sqrt();

        if (actual_norm - expected_norm).abs() > epsilon {
            // Here we must fail - using the `Failure` struct
            Failure::new(&self)
                .expected_it(format!("to have a euclidean norm within <{}> of <{}>",
                    epsilon, expected_norm))
                .but_it(format!("was <({}, {})>, with a euclidean norm of <{}>",
                    vector.x, vector.y, actual_norm))
                .fail()
        }

        // Here the test passes, so we return `self` for chaining
        self
    }
}

No runtime deps