Crate kernal

source ·
Expand description

Kernal 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

When you write an assertion over a value, you always start withassert_that!(<your value>). The assert_that macro gives you an instance on which you can call associated functions to make your assertions. To be able to use these assertions, the specialized extension traits must be imported, such as StringAssertions when using special assertions for Strings. You can glob-import the prelude module to get 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.

use kernal::prelude::*;

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. To do this, create a new trait which has a method for your assertion. This will be called on the output of the assert_that macro. In order to enable its usage, you need to implement your trait on the AssertThat with the type you want to test as a type parameter. Import the AssertThatData trait to get access to the tested data. It is not provided in the prelude module in order to avoid presenting these methods every time the user looks for an assertion. You can use the Failure struct to compose an error message consistent with the kernal crate. The example below demonstrates this process.

use kernal::{AssertThat, AssertThatData, Failure};
use kernal::prelude::*;

// 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
    }
}

assert_that!(Vector2f32 { x: 3.0, y: 4.0 }).has_euclidean_norm(5.0, 0.01);
assert_that!(|| assert_that!(Vector2f32 { x: 3.0, y: 3.0 }).has_euclidean_norm(5.0, 0.01))
    .panics();

Notes on performance

Should you write assertions on large amounts of data, the standard assertions may become a bottleneck. For some use cases, there are specialized assertions that use additional trait bounds to improve performance. These are available under the fast_prelude module. See below for an example on how to use it.

use kernal::prelude::*;
use kernal::fast_prelude::*;

assert_that!([1, 2, 3, 4, 5])
    .contains_all_of_using_hash([2, 3, 4])
    .contains_none_of_using_ord([6, 7, 8]);

If no sufficiently performant assertion is available, you should consider falling back to standard assertions.

Modules

Macros

  • This macro starts every assertion. It takes an expression and returns an AssertThat instance which allows to perform various assertions on the value generated by the expression. Remember to import prelude in order to get access to all assertions. That import also provides this macro.
  • A utility macro for constructing slices of assertion trait objects, that is, boxed functions which take some input and have no output. These are used as input to assertions which distribute items over assertions, such as OrderedCollectionAssertions::satisfies_exactly_in_given_order. It accepts patterns in the form of a list of lambda expressions, ignores the output of each one and wraps it in a box.

Structs

  • This struct holds the evaluated result of an expression for further assertions. It also contains metadata used for generating helpful error messages should an assertion fail.
  • This type is used to generate error messages of a specific format. It is mostly used internally, but exported to enable users to extend the assertions provided by this crate for custom types.

Traits