1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
//! Fluent test assertions in Rust //! //! Spectral is a testing framework designed to make your assertions read like plain English. //! This allows you to more easily expose the intent of your test, rather than having it shrouded by //! assertions which work, but are opaque on their meaning. //! //! Methods available to assert with are dependent upon the type of the subject under test. //! Assertions are available for some basic types, but there is still a great deal missing from the //! standard library. //! //! ## Usage //! //! Add the dependency to your `Cargo.toml`: //! //! ```toml //! [dependencies] //! spectral = "0.2.0" //! ``` //! //! Then add this to your crate: //! //! ```rust //! extern crate spectral; //! ``` //! //! If you want macro support, include `#[macro_use]` to the declaration: //! //! ```rust,ignore //! #[macro_use] //! extern crate spectral; //! ``` //! //! To quickly start using assertions, `use` the prelude module: //! //! ```rust //! use spectral::prelude::*; //! ``` //! //! ## Example //! //! We're going to make a few assertions on a `String` we create. Normally you would //! want to assert on the output of something, but we'll just pretend that something created it. //! //! First, we'll create a new test with our `String`. //! //! ```rust //! #[test] //! pub fn should_be_the_correct_string() { //! let subject = "Hello World!"; //! } //! ``` //! //! Note that it is good practice to make sure that you name your test in a way that actually //! explains what it is trying to test. When you have a number of tests, and one of them fails, //! something like this is easier to understand: //! //! ```rust,ignore //! #[test] //! pub fn should_return_false_if_condition_does_not_hold() { //! ... //! } //! ``` //! //! Rather than if you have a test like this: //! //! ```rust,ignore //! #[test] //! pub fn should_work() { //! ... //! } //! ``` //! //! Unfortunately, our test isn't named very well at the moment, but given the lack of context, //! it'll have to do for now. //! //! Now that we have something to test, we need to actually start asserting on it. The first part //! to that is to provide it to the `assert_that` function. Note that we need to provide it as a //! reference. //! //! ```rust //! #[test] //! pub fn should_be_the_correct_string() { //! let subject = "Hello World!"; //! assert_that(&subject); //! } //! ``` //! //! If we run that with `cargo test`, we'll see the following output: //! //! ```bash //! running 1 test //! test should_be_the_correct_string ... ok //! ``` //! //! Our test compiles and passes, but we still haven't made any assertions. Let's make a simple one //! to start with. We'll check to see that it starts with the letter 'H'. //! //! ```rust //! #[test] //! pub fn should_be_the_correct_string() { //! let subject = "Hello World!"; //! assert_that(&subject).starts_with(&"H"); //! } //! ``` //! //! Once you run this, you'll notice that the test still passes. That's because we've just proven //! something that was already true. Usually you'll want to start with a failing test, and then //! change your code to make it pass, rather than writing the test after the implementation. //! //! But for the purpose of exploration, let's break the actual value. We'll change "Hello World!" //! to be "ello World!". //! //! ```rust //! #[test] //! pub fn should_be_the_correct_string() { //! let subject = "ello World!"; //! assert_that(&subject).starts_with(&"H"); //! } //! ``` //! //! This time, we see that the test fails, and we also get some output from our assertion to tell //! us what it was, and what it was expected to be: //! //! ```bash //! running 1 test //! test should_be_the_correct_string ... FAILED //! //! failures: //! //! ---- should_be_the_correct_string stdout ---- //! thread 'should_be_the_correct_string' panicked at 'expected string starting with <"H"> but //! was <"ello World!">', src/lib.rs:204 //! ``` //! //! Great! So we've just encountered a failing test. This particular case is quite easy to fix up //! (just add the letter 'H' back to the start of the `String`), but we can also see that the panic //! message tells us enough information to work that out as well. //! //! Now, this was just a simple example, and there's a number of features not demonstrated, but //! hopefully it's enough to start you off with writing assertions in your tests using Spectral. use std::cmp::PartialEq; use std::fmt::Debug; pub mod numeric; pub mod option; pub mod prelude; pub mod result; pub mod string; pub mod vec; pub mod iter; #[macro_export] macro_rules! assert_that { (&$subject:ident$(.$additional_subject:ident)*) => { assert_that!($subject$(.$additional_subject)*) }; ($subject:ident$(.$additional_subject:ident)*) => { assert_that(&$subject$(.$additional_subject)*) }; } /// A description for an assertion. /// /// This is created by the `asserting` function. #[derive(Debug)] pub struct SpecDescription<'r> { value: &'r str, } /// An assertion. /// /// This is created by either the `assert_that` function, or by calling `that` on a /// `SpecDescription`. #[derive(Debug)] pub struct Spec<'s, S: 's> { pub subject: &'s S, description: Option<&'s str>, expected: Option<String>, actual: Option<String>, } /// Wraps a subject in a `Spec` to provide assertions against it. /// /// The subject must be a reference. pub fn assert_that<'s, S>(subject: &'s S) -> Spec<'s, S> { Spec { subject: subject, description: None, expected: None, actual: None, } } /// Describes an assertion. pub fn asserting<'r>(description: &'r str) -> SpecDescription { SpecDescription { value: description } } impl<'r> SpecDescription<'r> { /// Creates a new assertion, passing through its description. pub fn that<S>(self, subject: &'r S) -> Spec<'r, S> { Spec { subject: subject, description: Some(self.value), expected: None, actual: None, } } } impl<'s, S> Spec<'s, S> { /// Builder method to add the expected value for the panic message. pub fn with_expected(&mut self, expected: String) -> &mut Self { let mut spec = self; spec.expected = Some(expected); spec } /// Builder method to add the actual value for the panic message. pub fn with_actual(&mut self, actual: String) -> &mut Self { let mut spec = self; spec.actual = Some(actual); spec } /// Builds the failure message with a description (if present), the expected value, /// and the actual value and then calls `panic` with the created message. pub fn fail(&mut self) { if !self.expected.is_some() || !self.actual.is_some() { panic!("invalid assertion"); } match self.description { Some(description) => { panic!(format!("{}: expected {} but was {}", description, self.expected.clone().unwrap(), self.actual.clone().unwrap())) } None => { panic!(format!("expected {} but was {}", self.expected.clone().unwrap(), self.actual.clone().unwrap())) } } } /// Calls `panic` with the provided message, prepending the assertion description /// if present. fn fail_with_message(&mut self, message: String) { match self.description { Some(description) => panic!(format!("{}: {}", description, message)), None => panic!(message), } } } impl<'s, S> Spec<'s, S> where S: Debug + PartialEq { /// Asserts that the actual value and the expected value are equal. The value type must /// implement `PartialEq`. /// /// ```rust,ignore /// assert_that(&"hello").is_equal_to(&"hello"); /// ``` pub fn is_equal_to(&mut self, expected: &S) -> &mut Self { let subject = self.subject; if !subject.eq(expected) { self.with_expected(format!("<{:?}>", expected)) .with_actual(format!("<{:?}>", subject)) .fail(); } self } } impl<'s, S> Spec<'s, S> where S: Debug { /// Accepts a function accepting the value type which returns a bool. Returning false will /// cause the assertion to fail. /// /// NOTE: The resultant panic message will only state the actual value. It's recommended that /// you write your own assertion rather than relying upon this. /// /// ```rust,ignore /// assert_that(&"hello").matches(|x| x.eq(&"hello")); /// ``` pub fn matches<F>(&mut self, matching_function: F) -> &mut Self where F: Fn(&'s S) -> bool { let subject = self.subject; if !matching_function(subject) { self.fail_with_message(format!("expectation failed for value <{:?}>", subject)); } self } /// Transforms the subject of the `Spec` by passing it through to the provided mapping /// function. /// /// ```rust,ignore /// let test_struct = TestStruct { value: 5 }; /// assert_that(&test_struct).map(|val| &val.value).is_equal_to(&5); /// ``` pub fn map<F, T>(self, mapping_function: F) -> Spec<'s, T> where F: Fn(&'s S) -> &'s T { Spec { subject: mapping_function(self.subject), description: self.description, expected: self.expected, actual: self.actual, } } }