speculoos-0.11.0/.cargo_vcs_info.json0000644000000001360000000000100131270ustar { "git": { "sha1": "06b3432ad9efcae692e93dd1d677c0d70452027a" }, "path_in_vcs": "" }speculoos-0.11.0/.github/workflows/CI.yml000064400000000000000000000027641046102023000163430ustar 00000000000000name: CI on: push: branches: - main pull_request: branches: - main jobs: test: name: Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Install Rust run: rustup toolchain install stable --component llvm-tools-preview - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 with: files: lcov.info fail_ci_if_error: true lints: name: Lints & Format runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true components: rustfmt, clippy - name: Run cargo fmt uses: actions-rs/cargo@v1 continue-on-error: false with: command: fmt args: --all -- --check - name: Run cargo clippy uses: actions-rs/cargo@v1 continue-on-error: false with: command: clippy args: -- -D warnings speculoos-0.11.0/.gitignore000064400000000000000000000000441046102023000137050ustar 00000000000000target .idea Cargo.lock *.swp *.bk speculoos-0.11.0/Cargo.toml0000644000000016530000000000100111320ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "speculoos" version = "0.11.0" authors = [ "cfrancia ", "oknozor ", ] description = "Fluent test assertions" readme = "README.md" keywords = [ "fluent", "testing", "matchers", "assert", "assertions", ] license = "Apache-2.0" repository = "https://github.com/oknozor/speculoos" [dependencies.num] version = "0.4.0" optional = true [features] default = ["num"] speculoos-0.11.0/Cargo.toml.orig000064400000000000000000000007071046102023000146120ustar 00000000000000[package] name = "speculoos" version = "0.11.0" authors = [ "cfrancia ", "oknozor " ] license = "Apache-2.0" description = "Fluent test assertions" repository = "https://github.com/oknozor/speculoos" readme = "README.md" keywords = ["fluent", "testing", "matchers", "assert", "assertions"] [features] default = ["num"] [dependencies] num = { version = "0.4.0", optional = true } speculoos-0.11.0/LICENSE000064400000000000000000000261351046102023000127330ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. speculoos-0.11.0/README.md000064400000000000000000000551471046102023000132120ustar 00000000000000This is a fork the unmaintained crate [spectral](https://github.com/cfrancia/spectral). Spectral as not changed for five years and yet is still very usable, the goal of this fork is to add new assertion capabilities without breaking the existing API. # speculoos ![Crates.io](https://img.shields.io/crates/v/speculoos?style=plastic) [![CI](https://github.com/oknozor/speculoos/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/oknozor/speculoos/actions/workflows/CI.yml) [![codecov](https://codecov.io/gh/oknozor/speculoos/branch/main/graph/badge.svg?token=MZ45HO00YE)](https://codecov.io/gh/oknozor/speculoos) ![GitHub](https://img.shields.io/github/license/oknozor/speculoos) --- Fluent test assertions for Rust. Influenced by Google Truth and other fluent assertion frameworks. ## Usage Add this to your `Cargo.toml`: ```toml [dependencies] speculoos = "0.9.0" ``` To quickly start using assertions, simply use the `prelude` module in your test module: ```rust use speculoos::prelude::*; ``` ## Overview Speculoos allows you to write your assertions in a fluent manner by separating out what you are testing with, what you are testing against and how you are asserting. ### Simple asserts For example, to test that a produced value is equal to an expected value, you would write: ```rust assert_that(&1).is_equal_to(1); ``` Or that a Vec contains a certain number of elements: ```rust let test_vec = vec![1,2,3]; assert_that(&test_vec).has_length(3); ``` The methods avaliable for asserting depend upon the type under test and what traits are implemented. As described below, it's recommended to use the macro form of `assert_that!` to provide correct file and line numbers for failing assertions. ### Failure messages For failing assertions, the usual panic message follows the following format: ``` expected: <2> but was: <1> ``` To add additional clarification to the panic message, you can also deliberately state what you are asserting by calling the `asserting(...)` function rather than `assert_that(...)`: ```rust asserting(&"test condition").that(&1).is_equal_to(&2); ``` Which will produce: ``` test condition: expected: <2> but was: <1> ``` Using the macro form of `assert_that!` will provide you with the file and line of the failing assertion as well: ``` expected: vec to have length <2> but was: <1> at location: tests/parser.rs:112 ``` ### Named Subjects To make it more obvious what your subject actually is, you can call `.named(...)` after `assert_that` (or `asserting(...).that(...)`), which will print out the provided `&str` as the subject name if the assertion fails. ``` assert_that(&thing.attributes).named(&"thing attributes").has_length(2); ``` On failure, this will display: ``` for subject [thing attributes] expected: vec to have length <2> but was: <1> ``` ### Mapping values If you want to assert against a value contained within a struct, you can call `map(...)` with a closure, which will create a new `Spec` based upon the return value of the closure. You can then call any applicable assertions against the mapped value. ```rust let test_struct = TestStruct { value: 5 }; assert_that(&test_struct).map(|val| &val.value).is_equal_to(5); ``` ## Macros If you add `#[macro_use]` to the `extern crate` declaration, you can also use the macro form of `assert_that` and `asserting`. ```rust assert_that!(test_vec).has_length(5) ``` This allows you to pass through a subject to test without needing to deliberately turn it into a reference. However, for consistency, you can also use a deliberate reference in the macro as well. ```rust assert_that!(&test_vec).has_length(5) ``` Additionally, this will provide you with the file and line number of the failing assertion (rather than just the internal speculoos panic location). ## Assertions (Basic) Note: Descriptions and examples for each of the assertions are further down in this readme. ### General #### is_equal_to #### is_not_equal_to #### matches ### Booleans #### is_true #### is_false ### Numbers #### is_less_than #### is_less_than_or_equal_to #### is_greater_than #### is_greater_than_or_equal_to ### Floats (optional) #### is_close_to ### Options #### is_some -> (returns a new Spec with the Option value) #### is_none #### contains_value ### Paths #### exists #### does_not_exist #### is_a_file #### is_a_directory #### has_file_name ### Results #### is_ok -> (returns a new Spec with the Ok value) #### is_err -> (returns a new Spec with the Err value) #### is_ok_containing #### is_err_containing ### Strings #### starts_with #### ends_with #### contains #### is_empty ### Vectors #### has_length #### is_empty ### HashMaps #### has_length #### is_empty #### contains_key -> (returns a new Spec with the key value) #### does_not_contain_key #### contains_entry #### does_not_contain_entry ### HashSets #### has_length #### is_empty #### contains (from iterator) #### does_not_contain (from iterator) #### contains_all_of (from iterator) ### IntoIterator/Iterator #### contains #### does_not_contain #### contains_all_of #### mapped_contains #### equals_iterator ### IntoIterator #### matching_contains ## Optional Features ### Num Crate The `num` crate is used for `Float` assertions. This feature will be enabled by default, but if you don't want the dependency on `num`, then simply disable it. ## Assertions (Detailed) As a general note, any type under test will usually need to implement at least `Debug`. Other assertions will have varying bounds attached to them. ### General #### is_equal_to Asserts that the subject and the expected value are equal. The subject type must implement `PartialEq`. ##### Example ```rust assert_that(&"hello").is_equal_to(&"hello"); ``` ##### Failure Message ```bash expected: <2> but was: <1> ``` #### is_not_equal_to Asserts that the subject and the expected value are not equal. The subject type must implement `PartialEq`. ##### Example ```rust assert_that(&"hello").is_not_equal_to(&"hello"); ``` ##### Failure Message ```bash expected: <1> not equal to <1> but was: equal ``` #### matches Accepts a function accepting the subject 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 assertions rather than relying upon this. ##### Example ```rust assert_that(&"Hello").matches(|val| val.eq(&"Hello")); ``` ##### Failure Message ```bash expectation failed for value <"Hello"> ``` ### Booleans #### is_true Asserts that the subject is true. The subject type must be `bool`. ##### Example ```rust assert_that(&true).is_true(); ``` ##### Failure Message ```bash expected: bool to be but was: ``` #### is_false Asserts that the subject is false. The subject type must be `bool`. ##### Example ```rust assert_that(&false).is_false(); ``` ##### Failure Message ```bash expected: bool to be but was: ``` ### Numbers #### is_less_than Asserts that the subject value is less than the expected value. The subject type must implement `PartialOrd`. ##### Example ```rust assert_that(&1).is_less_than(&2); ``` ##### Failure Message ```bash expected: value less than <2> but was: <3> ``` #### is_less_than_or_equal_to Asserts that the subject is less than or equal to the expected value. The subject type must implement `PartialOrd`. ##### Example ```rust assert_that(&2).is_less_than_or_equal_to(&2); ``` ##### Failure Message ```bash expected: value less than or equal to <2> but was: <3> ``` #### is_greater_than Asserts that the subject is greater than the expected value. The subject type must implement `PartialOrd`. ##### Example ```rust assert_that(&2).is_greater_than(&1); ``` ##### Failure Message ```bash expected: value greater than <3> but was: <2> ``` #### is_greater_than_or_equal_to Asserts that the subject is greater than or equal to the expected value. The subject type must implement `PartialOrd`. ##### Example ```rust assert_that(&2).is_greater_than_or_equal_to(&1); ``` ##### Failure Message ```bash expected: value greater than or equal to <3> but was: <2> ``` ### Floats (optional) #### is_close_to Asserts that the subject is close to the expected value by the specified tolerance. The subject type must implement `Float` and `Debug`. ##### Example ```rust assert_that(&2.0f64).is_close_to(2.0f64, 0.01f64); ``` ##### Failure Message ```bash expected: float close to <1> (tolerance of <0.01>) but was: <2> ``` ### Options #### is_some -> (returns a new Spec with the Option value) Asserts that the subject is `Some`. The subject type must be an `Option`. This will return a new `Spec` containing the unwrapped value if it is `Some`. ##### Example ```rust assert_that(&Some(1)).is_some(); ``` ##### Chaining ```rust assert_that(&option).is_some().is_equal_to(&"Hello"); ``` ##### Failure Message ```bash expected: option[some] but was: option[none] ``` #### is_none Asserts that the subject is `None`. The value type must be an `Option`. ##### Example ```rust assert_that(&Option::None::).is_none(); ``` ##### Failure Message ```bash expected: option[none] but was: option<"Hello"> ``` #### contains_value Asserts that the subject is a `Some` containing the expected value. The subject type must be an `Option`. ##### Example ```rust assert_that(&Some(1)).contains_value(&1); ``` ##### Failure Message ```bash expected: option to contain <"Hi"> but was: <"Hello"> ``` ### Paths #### exists Asserts that the subject `Path` or `PathBuf` refers to an existing location. ##### Example ```rust assert_that(&Path::new("/tmp/file")).exists(); ``` ##### Failure Message ```bash expected: Path of <"/tmp/file"> to exist but was: a non-existent Path ``` #### does_not_exist Asserts that the subject `Path` or `PathBuf` does not refer to an existing location. ##### Example ```rust assert_that(&Path::new("/tmp/file")).does_not_exist(); ``` ##### Failure Message ```bash expected: Path of <"/tmp/file"> to not exist but was: a resolvable Path ``` #### is_a_file Asserts that the subject `Path` or `PathBuf` refers to an existing file. ##### Example ```rust assert_that(&Path::new("/tmp/file")).is_a_file(); ``` ##### Failure Message ```bash expected: Path of <"/tmp/file"> to be a file but was: not a resolvable file ``` #### is_a_directory Asserts that the subject `Path` or `PathBuf` refers to an existing directory. ##### Example ```rust assert_that(&Path::new("/tmp/dir/")).is_a_directory(); ``` ##### Failure Message ```bash expected: Path of <"/tmp/dir/"> to be a directory but was: not a resolvable directory ``` #### has_file_name Asserts that the subject `Path` or `PathBuf` has the expected file name. ##### Example ```rust assert_that(&Path::new("/tmp/file")).has_file_name(&"file"); ``` ##### Failure Message ```bash expected: Path with file name of but was: ``` ### Results #### is_ok -> (returns a new Spec with the Ok value) Asserts that the subject is `Ok`. The value type must be a `Result`. This will return a new `Spec` containing the unwrapped value if it is `Ok`. ##### Example ```rust assert_that(&Result::Ok::(1)).is_ok(); ``` ##### Chaining ```rust let result: Result<&str, &str> = Ok("Hello"); assert_that(&result).is_ok().is_equal_to(&"Hello"); ``` ##### Failure Message ```bash expected: result[ok] but was: result[error]<"Oh no"> ``` #### is_err -> (returns a new Spec with the Err value) Asserts that the subject is `Err`. The value type must be a `Result`. This will return a new `Spec` containing the unwrapped value if it is `Err`. Note: This used to be called `is_error`, but has been renamed to match standard Rust naming. ##### Example ```rust assert_that(&Result::Err::(1)).is_err(); ``` ##### Chaining ```rust let result: Result<&str, &str> = Err("Hello"); assert_that(&result).is_err().is_equal_to(&"Hello"); ``` ##### Failure Message ```bash expected: result[error] but was: result[ok]<"Hello"> ``` #### is_ok_containing Asserts that the subject is an `Ok` Result containing the expected value. The subject type must be a `Result`. ##### Example ```rust assert_that(&Result::Ok::(1)).is_ok_containing(&1); ``` ##### Failure Message ```bash expected: Result[ok] containing <"Hi"> but was: Result[ok] containing <"Hello"> ``` #### is_err_containing Asserts that the subject is an `Err` Result containing the expected value. The subject type must be a `Result`. ##### Example ```rust assert_that(&Result::Err::(1)).is_err_containing(&1); ``` ##### Failure Message ```bash expected: Result[err] containing <"Oh no"> but was: Result[err] containing <"Whoops"> ``` ### Strings #### starts_with Asserts that the subject `&str` or `String` starts with the provided `&str`. ##### Example ```rust assert_that(&"Hello").starts_with(&"H"); ``` ##### Failure Message ```bash expected: string starting with <"A"> but was: <"Hello"> ``` #### ends_with Asserts that the subject `&str` or `String` ends with the provided `&str`. ##### Example ```rust assert_that(&"Hello").ends_with(&"o"); ``` ##### Failure Message ```bash expected: string ending with <"A"> but was: <"Hello"> ``` #### contains Asserts that the subject `&str` or `String` contains the provided `&str`. ##### Example ```rust assert_that(&"Hello").contains(&"e"); ``` #### does_not_contain Asserts that the subject `&str` or `String` does not contain the provided `&str`. ##### Example ```rust assert_that(&"Hello").does_not_contain(&"Bonjour"); ``` ##### Failure Message ```bash expected: string containing <"A"> but was: <"Hello"> ``` #### is_empty Asserts that the subject `&str` or `String` represents an empty string. ##### Example ```rust assert_that(&"").is_empty(); ``` ##### Failure Message ```bash expected: an empty string but was: <"Hello"> ``` ### Vectors #### has_length Asserts that the length of the subject vector is equal to the provided length. The subject type must be of `Vec`. ##### Example ```rust assert_that(&vec![1, 2, 3, 4]).has_length(4); ``` ##### Failure Message ```bash expected: vec to have length <1> but was: <3> ``` #### is_empty Asserts that the subject vector is empty. The subject type must be of `Vec`. ##### Example ```rust let test_vec: Vec = vec![]; assert_that(&test_vec).is_empty(); ``` ##### Failure Message ```bash expected: an empty vec but was: a vec with length <1> ``` ### HashMaps #### has_length Asserts that the length of the subject hashmap is equal to the provided length. The subject type must be of `HashMap`. ##### Example ```rust let mut test_map = HashMap::new(); test_map.insert(1, 1); test_map.insert(2, 2); assert_that(&test_map).has_length(2); ``` ##### Failure Message ```bash expected: hashmap to have length <1> but was: <2> ``` #### is_empty Asserts that the subject hashmap is empty. The subject type must be of `HashMap`. ##### Example ```rust let test_map: HashMap = HashMap::new(); assert_that(&test_map).is_empty(); ``` ##### Failure Message ```bash expected: an empty hashmap but was: a hashmap with length <1> ``` #### contains_key -> (returns a new Spec with the key value) Asserts that the subject hashmap contains the expected key. The subject type must be of `HashMap`. This will return a new `Spec` containing the associated value if the key is present. ##### Example ```rust let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).contains_key(&"hello"); ``` ##### Chaining ```rust let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).contains_key(&"hello").is_equal_to(&"hi"); ``` ##### Failure Message ```bash expected: hashmap to contain key <"hello"> but was: <["hey", "hi"]> ``` #### does_not_contain_key Asserts that the subject hashmap does not contain the provided key. The subject type must be of `HashMap`. ##### Example ```rust let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).does_not_contain_key(&"hey"); ``` ##### Failure Message ```bash expected: hashmap to not contain key <"hello"> but was: present in hashmap ``` #### contains_entry Asserts that the subject hashmap contains the expected key with the expected value. The subject type must be of `HashMap`. ##### Example ```rust let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).contains_entry(&"hello", &"hi"); ``` ##### Failure Message ```bash expected: hashmap containing key <"hi"> with value <"hey"> but was: key <"hi"> with value <"hello"> instead ``` #### does_not_contain_entry Asserts that the subject hashmap does not contain the provided key and value. The subject type must be of `HashMap`. ##### Example ```rust let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).does_not_contain_entry(&"hello", &"hey"); ``` ##### Failure Message ```bash expected: hashmap to not contain key <"hello"> with value <"hi"> but was: present in hashmap ``` ### HashSets #### has_length Asserts that the length of the subject hashset is equal to the provided length. The subject type must be of `HashSet`. ##### Example ```rust let mut test_map = HashSet::new(); test_map.insert(1); test_map.insert(2); assert_that(&test_map).has_length(2); ``` ##### Failure Message ```bash expected: hashset to have length <1> but was: <2> ``` #### is_empty Asserts that the subject hashset is empty. The subject type must be of `HashSet`. ##### Example ```rust let test_map: HashSet = HashSet::new(); assert_that(&test_map).is_empty(); ``` ##### Failure Message ```bash expected: an empty hashset but was: a hashset with length <1> ``` #### Iterator based asserts Since a hashset is implements Iterator, all the Iterator assertions below also apply (e.g. contains & does_not_contain) ### IntoIterator/Iterator #### contains Asserts that the subject contains the provided value. The subject must implement `IntoIterator` or `Iterator`, and the contained type must implement `PartialEq` and `Debug`. ##### Example ```rust let test_vec = vec![1,2,3]; assert_that(&test_vec).contains(&2); ``` ##### Failure Message ```bash expected: iterator to contain <1> but was: <[5, 6]> ``` #### does_not_contain Asserts that the subject does not contain the provided value. The subject must implement `IntoIterator` or `Iterator`, and the contained type must implement `PartialEq` and `Debug`. ##### Example ```rust let test_vec = vec![1,2,3]; assert_that(&test_vec).does_not_contain(&4); ``` ##### Failure Message ```bash expected: iterator to not contain <1> but was: <[1, 2]> ``` #### contains_all_of Asserts that the subject contains all of the provided values. The subject must implement `IntoIterator` or `Iterator`, and the contained type must implement `PartialEq` and `Debug`. ##### Example ```rust let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).contains_all_of(&vec![&2, &3]); ``` ##### Failure Message ```bash expected: iterator to contain items <[1, 6]> but was: <[1, 2, 3]> ``` #### mapped_contains Maps the values of the subject before asserting that the mapped subject contains the provided value. The subject must implement IntoIterator, and the type of the mapped value must implement `PartialEq`. NOTE: The panic message will refer to the mapped values rather than the values present in the original subject. ##### Example ```rust #[derive(PartialEq, Debug)] struct Simple { pub val: usize, } ... assert_that(&vec![Simple { val: 1 }, Simple { val: 2 } ]).mapped_contains(|x| &x.val, &2); ``` ##### Failure Message ```bash expected: iterator to contain <5> but was: <[1, 2, 3]> ``` #### equals_iterator Asserts that the subject is equal to provided iterator. The subject must implement `IntoIterator` or `Iterator`, the contained type must implement `PartialEq` and `Debug` and the expected value must implement Iterator and Clone. ##### Example ```rust let expected_vec = vec![1,2,3]; let test_vec = vec![1,2,3]; assert_that(&test_vec).equals_iterator(&expected_vec.iter()); ``` ##### Failure Message ```bash expected: Iterator item of <4> (read <[1, 2]>) but was: Iterator item of <3> (read <[1, 2]>) ``` ### IntoIterator #### matching_contains Asserts that the subject contains a matching item by using the provided function. The subject must implement `IntoIterator`, and the contained type must implement `Debug`. ##### Example ```rust let mut test_into_iter = LinkedList::new(); test_into_iter.push_back(TestEnum::Bad); test_into_iter.push_back(TestEnum::Good); test_into_iter.push_back(TestEnum::Bad); assert_that(&test_into_iter).matching_contains(|val| { match val { &TestEnum::Good => true, _ => false } }); ``` ##### Failure Message ```bash expectation failed for iterator with values <[Bad, Bad, Bad]> ``` ## How it works The `Spec` struct implements a number of different bounded traits which provide assertions based upon the bound type. As a single example, length assertions are provided by the `VecAssertions` trait: ```rust pub trait VecAssertions { fn has_length(self, expected: usize); } ``` Which is then implemented by Spec: ```rust impl<'s, T> VecAssertions for Spec<'s, Vec> { fn has_length(self, expected: usize) { ... } } ``` Naturally traits need to be included with a `use` before they apply, but to avoid an excessive number of `use` statements there is a `prelude` module which re-exports commonly used assertion traits. ## Creating your own To create your own assertions, simply create a new trait containing your assertion methods and implement Spec against it. To fail an assertion, create a new `AssertionFailure` struct using `from_spec(...)` within your assertion method and pass in `self`. `AssertionFailure` also implements builder methods `with_expected(...)`, `with_actual(...)` and `fail(...)`, which provides the necessary functionality to fail the test with the usual message format. If you need greater control of the failure message, you can call `fail_with_message(...)` which will directly print the provided message. In either case, any description provided using `asserting(...)` will always be prepended to the panic message. For example, to create an assertion that the length of a `Vec` is at least a certain value: ```rust trait VecAtLeastLength { fn has_at_least_length(&mut self, expected: usize); } impl<'s, T> VecAtLeastLength for Spec<'s, Vec> { fn has_at_least_length(&mut self, expected: usize) { let subject = self.subject; if expected > subject.len() { AssertionFailure::from_spec(self) .with_expected(format!("vec with length at least <{}>", expected)) .with_actual(format!("<{}>", subject.len())) .fail(); } } } ``` speculoos-0.11.0/src/boolean.rs000064400000000000000000000034561046102023000145030ustar 00000000000000use super::{AssertionFailure, Spec}; pub trait BooleanAssertions { fn is_true(&mut self); fn is_false(&mut self); } impl<'s> BooleanAssertions for Spec<'s, bool> { /// Asserts that the subject is true. The subject type must be `bool`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&true).is_true(); /// ``` fn is_true(&mut self) { if !*self.subject { AssertionFailure::from_spec(self) .with_expected("bool to be ".to_string()) .with_actual("".to_string()) .fail(); } } /// Asserts that the subject is false. The subject type must be `bool`. /// /// ```rust, should_panic /// # use speculoos::prelude::*; /// assert_that(&true).is_false(); /// ``` fn is_false(&mut self) { if *self.subject { AssertionFailure::from_spec(self) .with_expected("bool to be ".to_string()) .with_actual("".to_string()) .fail(); } } } #[cfg(test)] mod tests { use super::super::prelude::*; #[test] pub fn should_not_panic_if_value_is_expected_to_be_true_and_is() { assert_that(&true).is_true(); } #[test] #[should_panic(expected = "\n\texpected: bool to be \n\t but was: ")] pub fn should_panic_if_value_is_expected_to_be_true_and_is_not() { assert_that(&false).is_true(); } #[test] pub fn should_not_panic_if_value_is_expected_to_be_false_and_is() { assert_that(&false).is_false(); } #[test] #[should_panic(expected = "\n\texpected: bool to be \n\t but was: ")] pub fn should_panic_if_value_is_expected_to_be_false_and_is_not() { assert_that(&true).is_false(); } } speculoos-0.11.0/src/hashmap.rs000064400000000000000000000351641046102023000145060ustar 00000000000000use super::{AssertionFailure, Spec}; use std::borrow::Borrow; use std::collections::HashMap; use std::fmt::Debug; use std::hash::Hash; pub trait HashMapAssertions<'s> { fn has_length(&mut self, expected: usize); fn is_empty(&mut self); fn is_not_empty(&mut self); } pub trait KeyHashMapAssertions<'s, K: Hash + Eq, V> { fn contains_key>(&mut self, expected_key: E) -> Spec<'s, V>; fn does_not_contain_key>(&mut self, expected_key: E); } pub trait EntryHashMapAssertions<'s, K: Hash + Eq, V: PartialEq> { fn contains_entry, F: Borrow>(&mut self, expected_key: E, expected_value: F); fn does_not_contain_entry, F: Borrow>( &mut self, expected_key: E, expected_value: F, ); } impl<'s, K, V> HashMapAssertions<'s> for Spec<'s, HashMap> where K: Hash + Eq + Debug, V: Debug, { /// Asserts that the length of the subject hashmap is equal to the provided length. The subject /// type must be of `HashMap`. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashMap; /// let mut test_map = HashMap::new(); /// test_map.insert(1, 1); /// test_map.insert(2, 2); /// /// assert_that(&test_map).has_length(2); /// ``` fn has_length(&mut self, expected: usize) { let subject = self.subject; if subject.len() != expected { AssertionFailure::from_spec(self) .with_expected(format!("hashmap to have length <{}>", expected)) .with_actual(format!("<{}>", subject.len())) .fail(); } } /// Asserts that the subject hashmap is empty. The subject type must be of `HashMap`. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashMap; /// let test_map: HashMap = HashMap::new(); /// assert_that(&test_map).is_empty(); /// ``` fn is_empty(&mut self) { let subject = self.subject; if !subject.is_empty() { AssertionFailure::from_spec(self) .with_expected("an empty hashmap".to_string()) .with_actual(format!("a hashmap with length <{:?}>", subject.len())) .fail(); } } /// Asserts that the subject hashmap is not empty. The subject type must be of `HashMap`. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashMap; /// let mut test_map: HashMap = HashMap::new(); /// test_map.insert(1, 2); /// assert_that(&test_map).is_not_empty(); /// ``` fn is_not_empty(&mut self) { let subject = self.subject; if subject.is_empty() { AssertionFailure::from_spec(self) .with_expected("an non empty hashmap".to_string()) .with_actual(format!("a hashmap with length <{:?}>", subject.len())) .fail(); } } } impl<'s, K, V> KeyHashMapAssertions<'s, K, V> for Spec<'s, HashMap> where K: Hash + Eq + Debug, V: Debug, { /// Asserts that the subject hashmap contains the expected key. The subject type must be /// of `HashMap`. /// /// This will return a new `Spec` containing the associated value if the key is present. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashMap; /// let mut test_map = HashMap::new(); /// test_map.insert("hello", "hi"); /// /// assert_that(&test_map).contains_key(&"hello"); /// ``` fn contains_key>(&mut self, expected_key: E) -> Spec<'s, V> { let subject = self.subject; let borrowed_expected_key = expected_key.borrow(); if let Some(value) = subject.get(borrowed_expected_key) { return Spec { subject: value, subject_name: self.subject_name, location: self.location.clone(), description: self.description, }; } let subject_keys: Vec<&K> = subject.keys().collect(); AssertionFailure::from_spec(self) .with_expected(format!( "hashmap to contain key <{:?}>", borrowed_expected_key )) .with_actual(format!("<{:?}>", subject_keys)) .fail(); unreachable!(); } /// Asserts that the subject hashmap does not contain the provided key. The subject type must be /// of `HashMap`. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashMap; /// let mut test_map = HashMap::new(); /// test_map.insert("hello", "hi"); /// /// assert_that(&test_map).does_not_contain_key(&"hey"); /// ``` fn does_not_contain_key>(&mut self, expected_key: E) { let subject = self.subject; let borrowed_expected_key = expected_key.borrow(); if subject.get(borrowed_expected_key).is_some() { AssertionFailure::from_spec(self) .with_expected(format!( "hashmap to not contain key <{:?}>", borrowed_expected_key )) .with_actual("present in hashmap".to_string()) .fail(); } } } impl<'s, K, V> EntryHashMapAssertions<'s, K, V> for Spec<'s, HashMap> where K: Hash + Eq + Debug, V: PartialEq + Debug, { /// Asserts that the subject hashmap contains the expected key with the expected value. /// The subject type must be of `HashMap`. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashMap; /// let mut test_map = HashMap::new(); /// test_map.insert("hello", "hi"); /// /// assert_that(&test_map).contains_entry(&"hello", &"hi"); /// ``` fn contains_entry, F: Borrow>(&mut self, expected_key: E, expected_value: F) { let subject = self.subject; let borrowed_expected_key = expected_key.borrow(); let borrowed_expected_value = expected_value.borrow(); let expected_message = format!( "hashmap containing key <{:?}> with value <{:?}>", borrowed_expected_key, borrowed_expected_value ); if let Some(value) = subject.get(borrowed_expected_key) { if value.eq(borrowed_expected_value) { return; } AssertionFailure::from_spec(self) .with_expected(expected_message) .with_actual(format!( "key <{:?}> with value <{:?}> instead", borrowed_expected_key, value )) .fail(); unreachable!(); } let subject_keys: Vec<&K> = subject.keys().collect(); AssertionFailure::from_spec(self) .with_expected(expected_message) .with_actual(format!("no matching key, keys are <{:?}>", subject_keys)) .fail(); } /// Asserts that the subject hashmap does not contains the provided key and value. /// The subject type must be of `HashMap`. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashMap; /// let mut test_map = HashMap::new(); /// test_map.insert("hello", "hi"); /// /// assert_that(&test_map).does_not_contain_entry(&"hello", &"hey"); /// ``` fn does_not_contain_entry, F: Borrow>( &mut self, expected_key: E, expected_value: F, ) { let subject = self.subject; let borrowed_expected_key = expected_key.borrow(); let borrowed_expected_value = expected_value.borrow(); if let Some(value) = subject.get(borrowed_expected_key) { if !value.eq(borrowed_expected_value) { return; } AssertionFailure::from_spec(self) .with_expected(format!( "hashmap to not contain key <{:?}> with value <{:?}>", borrowed_expected_key, borrowed_expected_value )) .with_actual("present in hashmap".to_string()) .fail(); } } } #[cfg(test)] mod tests { use super::super::prelude::*; use std::collections::HashMap; #[test] fn should_not_panic_if_hashmap_length_matches_expected() { let mut test_map = HashMap::new(); test_map.insert(1, 1); test_map.insert(2, 2); assert_that(&test_map).has_length(2); } #[test] fn should_not_panic_if_hashmap_length_matches_expected_and_value_is_not_partialeq() { #[derive(Debug)] struct NoPartialEq; let mut test_map = HashMap::new(); test_map.insert(1, NoPartialEq); test_map.insert(2, NoPartialEq); assert_that(&test_map).has_length(2); } #[test] #[should_panic(expected = "\n\texpected: hashmap to have length <1>\n\t but was: <2>")] fn should_panic_if_hashmap_length_does_not_match_expected() { let mut test_map = HashMap::new(); test_map.insert(1, 1); test_map.insert(2, 2); assert_that(&test_map).has_length(1); } #[test] fn should_not_panic_if_hashmap_was_expected_to_be_empty_and_is() { let test_map: HashMap = HashMap::new(); assert_that(&test_map).is_empty(); } #[test] fn should_not_panic_if_hashmap_was_expected_to_not_be_empty_and_is_not() { let mut test_map: HashMap = HashMap::new(); test_map.insert(1, 2); assert_that(&test_map).is_not_empty(); } #[test] #[should_panic(expected = "\n\texpected: an empty hashmap\ \n\t but was: a hashmap with length <1>")] fn should_panic_if_hashmap_was_expected_to_be_empty_and_is_not() { let mut test_map = HashMap::new(); test_map.insert(1, 1); assert_that(&test_map).is_empty(); } #[test] #[should_panic(expected = "\n\texpected: an non empty hashmap\ \n\t but was: a hashmap with length <0>")] fn should_panic_if_hashmap_was_expected_to_not_be_empty_and_is() { let test_map: HashMap = HashMap::new(); assert_that(&test_map).is_not_empty(); } #[test] fn contains_key_should_allow_multiple_borrow_forms() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).contains_key("hello"); assert_that(&test_map).contains_key(&mut "hello"); assert_that(&test_map).contains_key(&"hello"); } #[test] fn should_not_panic_if_hashmap_contains_key() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).contains_key(&"hello"); } #[test] // Unfortunately the order of the keys can change. Doesn't seem to make sense to sort them // just for the sake of checking the panic message. #[should_panic] fn should_not_panic_if_hashmap_does_not_contain_key() { let mut test_map = HashMap::new(); test_map.insert("hi", "hi"); test_map.insert("hey", "hey"); assert_that(&test_map).contains_key(&"hello"); } #[test] fn should_be_able_to_chain_value_from_contains_key() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map) .contains_key(&"hello") .is_equal_to(&"hi"); } #[test] fn does_not_contain_key_should_allow_multiple_borrow_forms() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).does_not_contain_key("hey"); assert_that(&test_map).does_not_contain_key(&mut "hey"); assert_that(&test_map).does_not_contain_key(&"hey"); } #[test] fn should_not_panic_if_hashmap_does_not_contain_key_when_expected() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).does_not_contain_key(&"hey"); } #[test] #[should_panic(expected = "\n\texpected: hashmap to not contain key <\"hello\">\ \n\t but was: present in hashmap")] fn should_panic_if_hashmap_does_contain_key_when_not_expected() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).does_not_contain_key(&"hello"); } #[test] fn contains_entry_should_allow_multiple_borrow_forms() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).contains_entry("hello", "hi"); assert_that(&test_map).contains_entry(&mut "hello", &mut "hi"); assert_that(&test_map).contains_entry("hello", &mut "hi"); assert_that(&test_map).contains_entry(&"hello", &"hi"); } #[test] fn should_not_panic_if_hashmap_contains_entry() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).contains_entry(&"hello", &"hi"); } #[test] #[should_panic( expected = "\n\texpected: hashmap containing key <\"hey\"> with value <\"hi\">\ \n\t but was: no matching key, keys are <[\"hello\"]>" )] fn should_panic_if_hashmap_contains_entry_without_key() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).contains_entry(&"hey", &"hi"); } #[test] #[should_panic( expected = "\n\texpected: hashmap containing key <\"hi\"> with value <\"hey\">\ \n\t but was: key <\"hi\"> with value <\"hello\"> instead" )] fn should_panic_if_hashmap_contains_entry_with_different_value() { let mut test_map = HashMap::new(); test_map.insert("hi", "hello"); assert_that(&test_map).contains_entry(&"hi", &"hey"); } #[test] fn should_not_panic_if_hashmap_does_not_contain_entry_if_expected() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).does_not_contain_entry(&"hey", &"hi"); } #[test] fn should_not_panic_if_hashmap_contains_entry_with_different_value_if_expected() { let mut test_map = HashMap::new(); test_map.insert("hi", "hello"); assert_that(&test_map).does_not_contain_entry(&"hi", &"hey"); } #[test] #[should_panic(expected = "\n\texpected: hashmap to not contain key <\"hello\"> \ with value <\"hi\">\ \n\t but was: present in hashmap")] fn should_panic_if_hashmap_contains_entry_if_not_expected() { let mut test_map = HashMap::new(); test_map.insert("hello", "hi"); assert_that(&test_map).does_not_contain_entry(&"hello", &"hi"); } } speculoos-0.11.0/src/hashset.rs000064400000000000000000000135131046102023000145160ustar 00000000000000use super::{AssertionFailure, Spec}; use std::collections::HashSet; use std::fmt::Debug; use std::hash::Hash; pub trait HashSetAssertions<'s> { fn has_length(&mut self, expected: usize); fn is_empty(&mut self); fn is_not_empty(&mut self); } impl<'s, K> HashSetAssertions<'s> for Spec<'s, HashSet> where K: Hash + Eq + Debug, { /// Asserts that the length of the subject HashSet is equal to the provided length. The subject /// type must be of `HashSet`. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashSet; /// let mut test_map = HashSet::new(); /// test_map.insert(1); /// test_map.insert(2); /// /// assert_that(&test_map).has_length(2); /// ``` fn has_length(&mut self, expected: usize) { let subject = self.subject; if subject.len() != expected { AssertionFailure::from_spec(self) .with_expected(format!("HashSet to have length <{}>", expected)) .with_actual(format!("<{}>", subject.len())) .fail(); } } /// Asserts that the subject HashSet is empty. The subject type must be of `HashSet`. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashSet; /// let test_map: HashSet = HashSet::new(); /// assert_that(&test_map).is_empty(); /// ``` fn is_empty(&mut self) { let subject = self.subject; if !subject.is_empty() { AssertionFailure::from_spec(self) .with_expected("an empty HashSet".to_string()) .with_actual(format!("a HashSet with length <{:?}>", subject.len())) .fail(); } } /// Asserts that the subject HashSet is not empty. The subject type must be of `HashSet`. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::collections::HashSet; /// let mut test_map: HashSet = HashSet::new(); /// test_map.insert(42); /// assert_that(&test_map).is_not_empty(); /// ``` fn is_not_empty(&mut self) { let subject = self.subject; if subject.is_empty() { AssertionFailure::from_spec(self) .with_expected("an non empty HashSet".to_string()) .with_actual(format!("a HashSet with length <{:?}>", subject.len())) .fail(); } } } #[cfg(test)] mod tests { use super::super::prelude::*; use std::collections::HashSet; #[test] fn should_not_panic_if_hash_set_length_matches_expected() { let mut test_map = HashSet::new(); test_map.insert(1); test_map.insert(2); assert_that(&test_map).has_length(2); } #[test] #[should_panic(expected = "\n\texpected: HashSet to have length <1>\n\t but was: <2>")] fn should_panic_if_hash_set_length_does_not_match_expected() { let mut test_map = HashSet::new(); test_map.insert(1); test_map.insert(2); assert_that(&test_map).has_length(1); } #[test] fn should_not_panic_if_hash_set_was_expected_to_be_empty_and_is() { let test_map: HashSet = HashSet::new(); assert_that(&test_map).is_empty(); } #[test] fn should_not_panic_if_hash_set_was_expected_to_not_be_empty_and_is_not() { let mut test_map: HashSet = HashSet::new(); test_map.insert(1); assert_that(&test_map).is_not_empty(); } #[test] #[should_panic(expected = "\n\texpected: an empty HashSet\ \n\t but was: a HashSet with length <1>")] fn should_panic_if_hash_set_was_expected_to_not_be_empty_and_is() { let mut test_map = HashSet::new(); test_map.insert(1); assert_that(&test_map).is_empty(); } #[test] #[should_panic(expected = "\n\texpected: an non empty HashSet\ \n\t but was: a HashSet with length <0>")] fn should_panic_if_hash_set_was_expected_to_be_empty_and_is_not() { let test_map: HashSet = HashSet::new(); assert_that(&test_map).is_not_empty(); } #[test] fn contains_should_allow_multiple_borrow_forms() { let mut test_map = HashSet::new(); test_map.insert("hello"); assert_that(&test_map).contains("hello"); assert_that(&test_map).contains(&mut "hello"); assert_that(&test_map).contains(&"hello"); } #[test] fn should_not_panic_if_hash_set_contains() { let mut test_map = HashSet::new(); test_map.insert("hello"); assert_that(&test_map).contains(&"hello"); } #[test] // Unfortunately the order of the keys can change. Doesn't seem to make sense to sort them // just for the sake of checking the panic message. #[should_panic] fn should_not_panic_if_hash_set_does_not_contain() { let mut test_map = HashSet::new(); test_map.insert("hi"); test_map.insert("hey"); assert_that(&test_map).contains(&"hello"); } #[test] fn does_not_contain_should_allow_multiple_borrow_forms() { let mut test_map = HashSet::new(); test_map.insert("hello"); assert_that(&test_map).does_not_contain("hey"); assert_that(&test_map).does_not_contain(&mut "hey"); assert_that(&test_map).does_not_contain(&"hey"); } #[test] fn should_not_panic_if_hash_set_does_not_contain_when_expected() { let mut test_map = HashSet::new(); test_map.insert("hello"); assert_that(&test_map).does_not_contain(&"hey"); } #[test] #[should_panic(expected = "\n\texpected: iterator to not contain <\"hello\">\ \n\t but was: <[\"hello\"]>")] fn should_panic_if_hash_set_does_contain_when_not_expected() { let mut test_map = HashSet::new(); test_map.insert("hello"); assert_that(&test_map).does_not_contain(&"hello"); } } speculoos-0.11.0/src/iter.rs000064400000000000000000000510641046102023000140250ustar 00000000000000use super::{AssertionFailure, Spec}; use std::borrow::Borrow; use std::cmp::PartialEq; use std::fmt::Debug; macro_rules! generate_iter_spec_trait { ($trait_name:ident) => { pub trait $trait_name<'s, T: 's> where T: Debug + PartialEq, { fn contains>(&mut self, expected_value: E); fn contains_all_of(&mut self, expected_values_iter: &'s E) where E: IntoIterator + Clone; fn does_not_contain>(&mut self, expected_value: E); fn equals_iterator(&mut self, expected_iter: &'s E) where E: Iterator + Clone; } }; } generate_iter_spec_trait!(ContainingIntoIterAssertions); generate_iter_spec_trait!(ContainingIteratorAssertions); pub trait MappingIterAssertions<'s, T: 's> where T: Debug, { fn matching_contains(&mut self, matcher: F) where F: Fn(&'s T) -> bool; fn mapped_contains(&mut self, mapping_function: F, expected_value: &M) where M: Debug + PartialEq, F: Fn(&'s T) -> M; } impl<'s, T: 's, I> ContainingIntoIterAssertions<'s, T> for Spec<'s, I> where T: Debug + PartialEq, &'s I: IntoIterator, { /// Asserts that the subject contains the provided value. The subject must implement /// `IntoIterator`, and the contained type must implement `PartialEq` and `Debug`. /// /// ```rust /// # use speculoos::prelude::*; /// let test_vec = vec![1,2,3]; /// assert_that(&test_vec).contains(&2); /// ``` fn contains>(&mut self, expected_value: E) { let subject_iter = self.subject.into_iter(); check_iterator_contains(self, subject_iter, expected_value, true); } /// Asserts that the subject contains all of the provided values. The subject must implement /// `IntoIterator`, and the contained type must implement `PartialEq` and `Debug`. /// /// ```rust /// # use speculoos::prelude::*; /// let test_vec = vec![1,2,3]; /// assert_that(&test_vec).contains_all_of(&vec![&2, &3]); /// ``` fn contains_all_of(&mut self, expected_values_iter: &'s E) where E: IntoIterator + Clone, { let subject_iter = self.subject.into_iter(); let expected_iter = expected_values_iter.clone().into_iter(); check_iterator_contains_all_of(self, subject_iter, expected_iter); } /// Asserts that the subject does not contain the provided value. The subject must implement /// `IntoIterator`, and the contained type must implement `PartialEq` and `Debug`. /// /// ```rust /// # use speculoos::prelude::*; /// let test_vec = vec![1,2,3]; /// assert_that(&test_vec).does_not_contain(&4); /// ``` fn does_not_contain>(&mut self, expected_value: E) { let subject_iter = self.subject.into_iter(); check_iterator_contains(self, subject_iter, expected_value, false); } /// Asserts that the subject is equal to provided iterator. The subject must implement /// `IntoIterator`, the contained type must implement `PartialEq` and `Debug` and the expected /// value must implement Iterator and Clone. /// /// ```rust /// # use speculoos::prelude::*; /// let expected_vec = vec![1,2,3]; /// let test_vec = vec![1,2,3]; /// assert_that(&test_vec).equals_iterator(&expected_vec.iter()); /// ``` fn equals_iterator(&mut self, expected_iter: &'s E) where E: Iterator + Clone, { compare_iterators(self, self.subject.into_iter(), expected_iter.clone()); } } impl<'s, T: 's, I> ContainingIteratorAssertions<'s, T> for Spec<'s, I> where T: Debug + PartialEq, I: Iterator + Clone, { /// Asserts that the iterable subject contains the provided value. The subject must implement /// `Iterator`, and the contained type must implement `PartialEq` and `Debug`. /// /// ```rust /// # use speculoos::prelude::*; /// let test_vec = vec![1,2,3]; /// assert_that(&test_vec.iter()).contains(&2); /// ``` fn contains>(&mut self, expected_value: E) { let subject_iter = self.subject.clone(); check_iterator_contains(self, subject_iter, expected_value, true); } /// Asserts that the subject contains all of the provided values. The subject must implement /// `Iterator`, and the contained type must implement `PartialEq` and `Debug`. /// /// ```rust /// let test_vec = vec![1,2,3]; /// # use speculoos::prelude::*; /// assert_that(&test_vec.iter()).contains_all_of(&vec![&2, &3]); /// ``` fn contains_all_of(&mut self, expected_values_iter: &'s E) where E: IntoIterator + Clone, { let subject_iter = self.subject.clone(); let expected_iter = expected_values_iter.clone().into_iter(); check_iterator_contains_all_of(self, subject_iter, expected_iter); } /// Asserts that the iterable subject does not contain the provided value. The subject must /// implement `Iterator`, and the contained type must implement `PartialEq` and `Debug`. /// /// ```rust /// let test_vec = vec![1,2,3]; /// # use speculoos::prelude::*; /// assert_that(&test_vec.iter()).does_not_contain(&4); /// ``` fn does_not_contain>(&mut self, expected_value: E) { let subject_iter = self.subject.clone(); check_iterator_contains(self, subject_iter, expected_value, false); } /// Asserts that the iterable subject is equal to provided iterator. The subject must implement /// `Iterator`, the contained type must implement `PartialEq` and `Debug` and the expected /// value must implement Iterator and Clone. /// /// ```rust /// let expected_vec = vec![1,2,3]; /// let test_vec = vec![1,2,3]; /// # use speculoos::prelude::*; /// assert_that(&test_vec.iter()).equals_iterator(&expected_vec.iter()); /// ``` fn equals_iterator(&mut self, expected_iter: &'s E) where E: Iterator + Clone, { compare_iterators(self, self.subject.clone(), expected_iter.clone()); } } impl<'s, T: 's, I> MappingIterAssertions<'s, T> for Spec<'s, I> where T: Debug, &'s I: IntoIterator, { /// Asserts that the subject contains a matching item by using the provided function. /// The subject must implement `IntoIterator`, and the contained type must implement `Debug`. /// /// ```rust /// # use std::collections::LinkedList; /// # use speculoos::prelude::*; /// #[derive(Debug, PartialEq)] /// # pub enum TestEnum { Good, Bad } /// let mut test_into_iter = LinkedList::new(); /// test_into_iter.push_back(TestEnum::Bad); /// test_into_iter.push_back(TestEnum::Good); /// test_into_iter.push_back(TestEnum::Bad); /// /// assert_that(&test_into_iter).matching_contains(|val| { /// match val { /// &TestEnum::Good => true, /// _ => false /// } /// }); /// ``` fn matching_contains(&mut self, matcher: F) where F: Fn(&'s T) -> bool, { let mut actual = Vec::new(); for x in self.subject { if matcher(x) { return; } else { actual.push(x); } } AssertionFailure::from_spec(self).fail_with_message(format!( "expectation failed for iterator with values <{:?}>", actual )); } /// Maps the values of the subject before asserting that the mapped subject contains the /// provided value. The subject must implement IntoIterator, and the type of the mapped /// value must implement `PartialEq`. /// /// NOTE: The panic message will refer to the mapped values rather than the values present in /// the original subject. /// /// ```rust /// #[derive(PartialEq, Debug)] /// struct Simple { /// pub val: usize, /// } /// /// //... /// # use speculoos::prelude::*; /// assert_that(&vec![Simple { val: 1 }, Simple { val: 2 } ]).mapped_contains(|x| x.val, &2); /// ``` fn mapped_contains(&mut self, mapping_function: F, expected_value: &M) where M: Debug + PartialEq, F: Fn(&'s T) -> M, { let subject = self.subject; let mapped_vec: Vec = subject.into_iter().map(mapping_function).collect(); if mapped_vec.contains(expected_value) { return; } panic_unmatched(self, expected_value, mapped_vec, true); } } fn check_iterator_contains<'s, T, V: 's, I, E: Borrow>( spec: &mut Spec, actual_iter: I, expected_value: E, should_contain: bool, ) where V: PartialEq + Debug, I: Iterator, { let borrowed_expected_value = expected_value.borrow(); let mut contains_value = false; let mut actual = Vec::new(); for x in actual_iter { if borrowed_expected_value.eq(x) { contains_value = true; } actual.push(x); } if contains_value != should_contain { panic_unmatched(spec, borrowed_expected_value, actual, should_contain); } } fn check_iterator_contains_all_of( spec: &mut Spec, actual_iter: I, expected_values_iter: E, ) where V: PartialEq + Debug, I: Iterator, E: Iterator, { let actual_values: Vec = actual_iter.collect(); let mut matched_indexes = vec![]; let mut matched_indexes_holder = vec![]; let mut matched_values = vec![]; let mut unmatched_values = vec![]; 'outer: for expected in expected_values_iter { matched_indexes.append(&mut matched_indexes_holder); for (index, actual) in actual_values .iter() .enumerate() .filter(|&(i, _)| !matched_indexes.contains(&i)) { if expected.eq(actual) { matched_indexes_holder.push(index); matched_values.push(expected); continue 'outer; } } unmatched_values.push(expected); } if !unmatched_values.is_empty() { let mut expected_values: Vec = vec![]; expected_values.append(&mut matched_values); expected_values.append(&mut unmatched_values); AssertionFailure::from_spec(spec) .with_expected(format!("iterator to contain items <{:?}>", expected_values)) .with_actual(format!("<{:?}>", actual_values)) .fail(); } } fn compare_iterators(spec: &mut Spec, actual_iter: I, expected_iter: E) where V: PartialEq + Debug, I: Iterator, E: Iterator, { let mut actual_iter = actual_iter; let mut expected_iter = expected_iter; let mut read_subject = vec![]; let mut read_expected = vec![]; loop { match (actual_iter.next(), expected_iter.next()) { (Some(actual), Some(expected)) => { if !&actual.eq(&expected) { AssertionFailure::from_spec(spec) .with_expected(format!( "Iterator item of <{:?}> (read <{:?}>)", expected, read_expected )) .with_actual(format!( "Iterator item of <{:?}> (read <{:?}>)", actual, read_subject )) .fail(); unreachable!(); } read_subject.push(actual); read_expected.push(expected); } (Some(actual), None) => { AssertionFailure::from_spec(spec) .with_expected(format!("Completed iterator (read <{:?}>)", read_expected)) .with_actual(format!( "Iterator item of <{:?}> (read <{:?}>", actual, read_subject )) .fail(); unreachable!(); } (None, Some(expected)) => { AssertionFailure::from_spec(spec) .with_expected(format!( "Iterator item of <{:?}> (read <{:?}>", expected, read_expected )) .with_actual(format!("Completed iterator (read <{:?}>", read_subject)) .fail(); unreachable!(); } (None, None) => { break; } } } } fn panic_unmatched( spec: &mut Spec, expected: E, actual: A, should_contain: bool, ) { let condition = { if should_contain { " " } else { " not " } }; AssertionFailure::from_spec(spec) .with_expected(format!("iterator to{}contain <{:?}>", condition, expected)) .with_actual(format!("<{:?}>", actual)) .fail(); } #[cfg(test)] mod tests { use super::super::prelude::*; use std::collections::LinkedList; #[test] fn contains_should_allow_for_multiple_borrow_types_for_intoiter() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec).contains(2); assert_that(&test_vec).contains(&mut 2); assert_that(&test_vec).contains(&2); } #[test] fn should_not_panic_if_vec_contains_value() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec).contains(&2); } #[test] #[should_panic(expected = "\n\texpected: iterator to contain <5>\n\t but was: <[1, 2, 3]>")] fn should_panic_if_vec_does_not_contain_value() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec).contains(&5); } #[test] fn should_not_panic_if_vec_does_not_contain_value_if_expected() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec).does_not_contain(&4); } #[test] #[should_panic(expected = "\n\texpected: iterator to not contain <2>\n\t but was: <[1, 2, 3]>")] fn should_panic_if_vec_does_contain_value_and_expected_not_to() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec).does_not_contain(&2); } #[test] fn should_not_panic_if_iterable_contains_value() { let mut test_into_iter = LinkedList::new(); test_into_iter.push_back(1); test_into_iter.push_back(2); test_into_iter.push_back(3); assert_that(&test_into_iter).contains(&2); } #[test] #[should_panic(expected = "\n\texpected: iterator to contain <5>\n\t but was: <[1, 2, 3]>")] fn should_panic_if_iterable_does_not_contain_value() { let mut test_into_iter = LinkedList::new(); test_into_iter.push_back(1); test_into_iter.push_back(2); test_into_iter.push_back(3); assert_that(&test_into_iter).contains(&5); } #[test] fn should_not_panic_if_iterable_contains_all_expected_values() { let mut test_into_iter = LinkedList::new(); test_into_iter.push_back(1); test_into_iter.push_back(2); test_into_iter.push_back(3); assert_that(&test_into_iter).contains_all_of(&vec![&2, &3]); } #[test] #[should_panic(expected = "\n\texpected: iterator to contain items <[1, 6]>\ \n\t but was: <[1, 2, 3]>")] fn should_panic_if_iterable_does_not_contain_all_expected_values() { let mut test_into_iter = LinkedList::new(); test_into_iter.push_back(1); test_into_iter.push_back(2); test_into_iter.push_back(3); assert_that(&test_into_iter).contains_all_of(&vec![&1, &6]); } #[test] fn should_not_panic_if_iterator_contains_all_expected_values() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).contains_all_of(&vec![&2, &3]); } #[test] #[should_panic(expected = "\n\texpected: iterator to contain items <[1, 6]>\ \n\t but was: <[1, 2, 3]>")] fn should_panic_if_iterator_does_not_contain_all_expected_values() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).contains_all_of(&vec![&1, &6]); } #[test] #[should_panic(expected = "\n\texpected: iterator to contain items <[1, 3, 1]>\ \n\t but was: <[1, 2, 3]>")] fn should_panic_if_iterator_does_not_contain_all_expected_values_exactly() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).contains_all_of(&vec![&1, &1, &3]); } #[test] fn should_not_panic_if_iteratable_equals_expected_iterator() { let expected_vec = vec![1, 2, 3]; let test_vec = vec![1, 2, 3]; assert_that(&test_vec).equals_iterator(&expected_vec.iter()); } #[test] #[should_panic(expected = "\n\texpected: Iterator item of <4> (read <[1, 2]>)\ \n\t but was: Iterator item of <3> (read <[1, 2]>)")] fn should_panic_if_iteratable_does_not_equal_expected_iterator() { let expected_vec = vec![1, 2, 4]; let test_vec = vec![1, 2, 3]; assert_that(&test_vec).equals_iterator(&expected_vec.iter()); } #[test] fn contains_should_allow_for_multiple_borrow_types_for_iterators() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).contains(2); assert_that(&test_vec.iter()).contains(&mut 2); assert_that(&test_vec.iter()).contains(&2); } #[test] fn should_not_panic_if_iterator_contains_value() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).contains(&2); } #[test] #[should_panic(expected = "\n\texpected: iterator to contain <5>\n\t but was: <[1, 2, 3]>")] fn should_panic_if_iterator_does_not_contain_value() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).contains(&5); } #[test] fn should_not_panic_if_iterator_does_not_contain_value_if_expected() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).does_not_contain(&4); } #[test] #[should_panic(expected = "\n\texpected: iterator to not contain <2>\n\t but was: <[1, 2, 3]>")] fn should_panic_if_iterator_does_contain_value_but_expected_not_to() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).does_not_contain(&2); } #[test] fn should_not_panic_if_iterator_equals_expected_iterator() { let expected_vec = vec![1, 2, 3]; let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).equals_iterator(&expected_vec.iter()); } #[test] #[should_panic(expected = "\n\texpected: Iterator item of <4> (read <[1, 2]>)\ \n\t but was: Iterator item of <3> (read <[1, 2]>)")] fn should_panic_if_iterator_does_not_equal_expected_iterator() { let expected_vec = vec![1, 2, 4]; let test_vec = vec![1, 2, 3]; assert_that(&test_vec.iter()).equals_iterator(&expected_vec.iter()); } #[test] fn should_not_panic_if_iterator_matches_on_value() { let mut test_into_iter = LinkedList::new(); test_into_iter.push_back(TestEnum::Bad); test_into_iter.push_back(TestEnum::Good); test_into_iter.push_back(TestEnum::Bad); assert_that(&test_into_iter).matching_contains(|val| match val { &TestEnum::Good => true, _ => false, }); } #[test] #[should_panic(expected = "\n\texpectation failed for iterator with values <[Bad, Bad, Bad]>")] fn should_panic_if_iterator_does_not_match_on_value() { let mut test_into_iter = LinkedList::new(); test_into_iter.push_back(TestEnum::Bad); test_into_iter.push_back(TestEnum::Bad); test_into_iter.push_back(TestEnum::Bad); assert_that(&test_into_iter).matching_contains(|val| match val { &TestEnum::Good => true, _ => false, }); } #[test] fn should_not_panic_if_vec_contains_mapped_value() { let test_vec = vec![TestStruct { value: 5 }, TestStruct { value: 6 }]; assert_that(&test_vec).mapped_contains(|val| val.value, &5); } #[test] #[should_panic(expected = "\n\texpected: iterator to contain <1>\n\t but was: <[5, 6]>")] fn should_panic_if_vec_does_not_contain_mapped_value() { let test_vec = vec![TestStruct { value: 5 }, TestStruct { value: 6 }]; assert_that(&test_vec).mapped_contains(|val| val.value, &1); } #[derive(Debug, PartialEq)] struct TestStruct { pub value: u8, } #[derive(Debug)] enum TestEnum { Good, Bad, } } speculoos-0.11.0/src/lib.rs000064400000000000000000000473311046102023000136320ustar 00000000000000#![allow(clippy::wrong_self_convention)] //! Fluent test assertions in Rust //! //! Speculoos 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] //! speculoos = "0.6.0" //! ``` //! //! Then add this to your crate: //! //! ```rust //! extern crate speculoos; //! ``` //! //! If you want macro support, include `#[macro_use]` to the declaration: //! //! ```rust, ignore //! #[macro_use] //! extern crate speculoos; //! ``` //! //! To quickly start using assertions, `use` the prelude module: //! //! ```rust //! use speculoos::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 //! #[test] //! pub fn should_return_false_if_condition_does_not_hold() { //! // ... //! } //! ``` //! //! Rather than if you have a test like this: //! //! ```rust //! #[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 //! # use speculoos::prelude::*; //! #[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 //! # use speculoos::prelude::*; //! #[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 //! # use speculoos::prelude::*; //! #[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 Speculoos. use std::borrow::Borrow; use std::cmp::PartialEq; use std::fmt::Debug; use colours::{TERM_BOLD, TERM_RED, TERM_RESET}; pub mod boolean; pub mod hashmap; pub mod hashset; pub mod iter; pub mod numeric; pub mod option; pub mod path; pub mod prelude; pub mod result; pub mod string; pub mod vec; // Disable colours during tests, otherwise trying to assert on the panic message becomes // significantly more annoying. #[cfg(not(test))] mod colours { pub const TERM_RED: &str = "\x1B[31m"; pub const TERM_BOLD: &str = "\x1B[1m"; pub const TERM_RESET: &str = "\x1B[0m"; } #[cfg(test)] mod colours { pub const TERM_RED: &str = ""; pub const TERM_BOLD: &str = ""; pub const TERM_RESET: &str = ""; } #[cfg(feature = "num")] extern crate num; #[macro_export] macro_rules! assert_that { (&$subject:tt) => { assert_that!($subject) }; ($subject:tt) => {{ let line = line!(); let file = file!(); assert_that(&$subject).at_location(format!("{}:{}", file, line)) }}; (&$subject:expr) => { assert_that!($subject) }; ($subject:expr) => {{ let line = line!(); let file = file!(); assert_that(&$subject).at_location(format!("{}:{}", file, line)) }}; } #[macro_export] macro_rules! asserting { (&$description:tt) => { asserting!($description) }; ($description:tt) => {{ let line = line!(); let file = file!(); asserting(&$description).at_location(format!("{}:{}", file, line)) }}; } pub trait DescriptiveSpec<'r> { fn subject_name(&self) -> Option<&'r str>; fn location(&self) -> Option; fn description(&self) -> Option<&'r str>; } /// A failed assertion. /// /// This exposes builder methods to construct the final failure message. #[derive(Debug)] pub struct AssertionFailure<'r, T: 'r> { spec: &'r T, expected: Option, actual: Option, } /// A description for an assertion. /// /// This is created by the `asserting` function. #[derive(Debug)] pub struct SpecDescription<'r> { value: &'r str, location: Option, } /// 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, pub subject_name: Option<&'s str>, pub location: Option, pub description: Option<&'s str>, } /// Wraps a subject in a `Spec` to provide assertions against it. /// /// The subject must be a reference. pub fn assert_that(subject: &S) -> Spec { Spec { subject, subject_name: None, location: None, description: None, } } /// Describes an assertion. pub fn asserting(description: &str) -> SpecDescription { SpecDescription { value: description, location: None, } } impl<'r> SpecDescription<'r> { pub fn at_location(self, location: String) -> Self { let mut description = self; description.location = Some(location); description } /// Creates a new assertion, passing through its description. pub fn that(self, subject: &'r S) -> Spec<'r, S> { Spec { subject, subject_name: None, location: self.location, description: Some(self.value), } } } impl<'r, T> DescriptiveSpec<'r> for Spec<'r, T> { fn subject_name(&self) -> Option<&'r str> { self.subject_name } fn location(&self) -> Option { self.location.clone() } fn description(&self) -> Option<&'r str> { self.description } } impl<'r, T: DescriptiveSpec<'r>> AssertionFailure<'r, T> { /// Construct a new AssertionFailure from a DescriptiveSpec. pub fn from_spec(spec: &'r T) -> AssertionFailure<'r, T> { AssertionFailure { spec, expected: None, actual: None, } } /// Builder method to add the expected value for the panic message. pub fn with_expected(&mut self, expected: String) -> &mut Self { let mut assertion = self; assertion.expected = Some(expected); assertion } /// Builder method to add the actual value for the panic message. pub fn with_actual(&mut self, actual: String) -> &mut Self { let mut assertion = self; assertion.actual = Some(actual); assertion } /// 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) { assert!( !(self.expected.is_none() || self.actual.is_none()), "invalid assertion" ); let location = self.maybe_build_location(); let subject_name = self.maybe_build_subject_name(); let description = self.maybe_build_description(); panic!( "{}{}\n\t{}expected: {}\n\t but was: {}{}\n{}", description, subject_name, TERM_RED, self.expected.clone().unwrap(), self.actual.clone().unwrap(), TERM_RESET, location ) } /// Calls `panic` with the provided message, prepending the assertion description /// if present. fn fail_with_message(&mut self, message: String) { let location = self.maybe_build_location(); let subject_name = self.maybe_build_subject_name(); let description = self.maybe_build_description(); panic!( "{}{}\n\t{}{}{}\n{}", description, subject_name, TERM_RED, message, TERM_RESET, location ) } fn maybe_build_location(&self) -> String { match self.spec.location() { Some(value) => format!("\n\t{}at location: {}{}\n", TERM_BOLD, value, TERM_RESET), None => "".to_string(), } } fn maybe_build_description(&self) -> String { match self.spec.description() { Some(value) => format!("\n\t{}{}:{}", TERM_BOLD, value, TERM_RESET), None => "".to_string(), } } fn maybe_build_subject_name(&self) -> String { match self.spec.subject_name() { Some(value) => format!("\n\t{}for subject [{}]{}", TERM_BOLD, value, TERM_RESET), None => "".to_string(), } } } impl<'s, S> Spec<'s, S> { /// Provides the actual location of the assertion. /// /// Usually you would not call this directly, but use the macro forms of `assert_that` and /// `asserting`, which will call this on your behalf with the correct location. pub fn at_location(self, location: String) -> Self { let mut spec = self; spec.location = Some(location); spec } /// Associates a name with the subject. /// /// This will be displayed if the assertion fails. pub fn named(self, subject_name: &'s str) -> Self { let mut spec = self; spec.subject_name = Some(subject_name); spec } } 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 /// # use speculoos::prelude::*; /// assert_that(&"hello").is_equal_to(&"hello"); /// ``` pub fn is_equal_to>(&mut self, expected: E) { let subject = self.subject; let borrowed_expected = expected.borrow(); if !subject.eq(borrowed_expected) { AssertionFailure::from_spec(self) .with_expected(format!("<{:?}>", borrowed_expected)) .with_actual(format!("<{:?}>", subject)) .fail(); } } /// Asserts that the actual value and the expected value are not equal. The value type must /// implement `PartialEq`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&"hello").is_not_equal_to(&"olleh"); /// ``` pub fn is_not_equal_to>(&mut self, expected: E) { let subject = self.subject; let borrowed_expected = expected.borrow(); if subject.eq(borrowed_expected) { AssertionFailure::from_spec(self) .with_expected(format!( "<{:?}> not equal to <{:?}>", subject, borrowed_expected )) .with_actual("equal".to_string()) .fail(); } } } 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. /// /// `matches` returns &mut &Self, making it possible to chain multiple assertions. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&"hello").matches(|x| x.eq(&"hello")); /// ``` pub fn matches(&mut self, matching_function: F) -> &mut Self where F: Fn(&'s S) -> bool, { let subject = self.subject; if !matching_function(subject) { AssertionFailure::from_spec(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 /// # use speculoos::prelude::*; /// # #[derive(Debug, PartialEq)] /// # struct TestStruct { /// # pub value: u8, /// # } /// let test_struct = TestStruct { value: 5 }; /// assert_that(&test_struct).map(|val| &val.value).is_equal_to(&5); /// ``` pub fn map(self, mapping_function: F) -> Spec<'s, T> where F: Fn(&'s S) -> &'s T, { Spec { subject: mapping_function(self.subject), subject_name: self.subject_name, location: self.location.clone(), description: self.description, } } } #[cfg(test)] mod tests { use super::prelude::*; #[test] fn should_be_able_to_use_macro_form_with_deliberate_reference() { let test_vec = vec![1, 2, 3, 4, 5]; assert_that!(&test_vec).mapped_contains(|val| val * 2, &6); } #[test] fn should_be_able_to_use_macro_form_without_deliberate_reference() { let test_vec = vec![1, 2, 3, 4, 5]; assert_that!(test_vec).mapped_contains(|val| val * 2, &6); } #[test] fn should_be_able_to_use_function_call_with_macro() { struct Line { x0: i32, x1: i32, } impl Line { fn get_delta_x(&self) -> i32 { (self.x1 - self.x0).abs() } } let line = Line { x0: 1, x1: 3 }; assert_that!(line.get_delta_x()).is_equal_to(2); assert_that!(&line.get_delta_x()).is_equal_to(2); } #[test] #[should_panic(expected = "\n\ttest condition:\n\texpected: <2>\n\t but was: <1>")] fn should_contain_assertion_description_in_panic() { asserting("test condition").that(&1).is_equal_to(&2); } #[test] #[should_panic(expected = "\n\tclosure:\n\texpectation failed for value <\"Hello\">")] fn should_contain_assertion_description_if_message_is_provided() { let value = "Hello"; asserting("closure") .that(&value) .matches(|val| val.eq(&"Hi")); } #[test] #[should_panic(expected = "\n\texpected: <2>\n\t but was: <1>\ \n\n\tat location: src/lib.rs:")] fn should_contain_file_and_line_in_panic_for_assertions() { assert_that!(&1).is_equal_to(&2); } #[test] #[should_panic(expected = "\n\texpectation failed for value <\"Hello\">\ \n\n\tat location: src/lib.rs:")] fn should_contain_file_and_line_for_assertions_if_message_is_provided() { let value = "Hello"; assert_that!(&value).matches(|val| val.eq(&"Hi")); } #[test] #[should_panic(expected = "\n\ttest condition:\n\texpected: <2>\n\t but was: <1>\ \n\n\tat location: src/lib.rs:")] fn should_contain_file_and_line_in_panic_for_descriptive_assertions() { asserting!(&"test condition").that(&1).is_equal_to(&2); } #[test] #[should_panic(expected = "\n\tclosure:\n\texpectation failed for value <\"Hello\">\ \n\n\tat location: src/lib.rs:")] fn should_contain_file_and_line_for_descriptive_assertions_if_message_is_provided() { let value = "Hello"; asserting!(&"closure") .that(&value) .matches(|val| val.eq(&"Hi")); } #[test] #[should_panic( expected = "\n\tfor subject [number one]\n\texpected: <2>\n\t but was: <1>\ \n\n\tat location: src/lib.rs:" )] fn should_contain_subject_name_in_panic_for_assertions() { assert_that!(&1).named("number one").is_equal_to(&2); } #[test] #[should_panic( expected = "\n\tfor subject [a word]\n\texpectation failed for value <\"Hello\">\ \n\n\tat location: src/lib.rs:" )] fn should_contain_subject_name_in_panic_for_assertions_if_message_is_provided() { let value = "Hello"; assert_that!(&value) .named("a word") .matches(|val| val.eq(&"Hi")); } #[test] fn is_equal_to_should_support_multiple_borrow_forms() { assert_that(&1).is_equal_to(1); assert_that(&1).is_equal_to(&mut 1); assert_that(&1).is_equal_to(&1); } #[test] fn should_not_panic_on_equal_subjects() { assert_that(&1).is_equal_to(&1); } #[test] #[should_panic(expected = "\n\texpected: <2>\n\t but was: <1>")] fn should_panic_on_unequal_subjects() { assert_that(&1).is_equal_to(&2); } #[test] fn is_not_equal_to_should_support_multiple_borrow_forms() { assert_that(&1).is_not_equal_to(2); assert_that(&1).is_not_equal_to(&mut 2); assert_that(&1).is_not_equal_to(&2); } #[test] fn should_not_panic_on_unequal_subjects_if_expected() { assert_that(&1).is_not_equal_to(&2); } #[test] #[should_panic(expected = "\n\texpected: <1> not equal to <1>\n\t but was: equal")] fn should_panic_on_equal_subjects_if_expected_unequal() { assert_that(&1).is_not_equal_to(&1); } #[test] fn should_not_panic_if_value_matches() { let value = "Hello"; assert_that(&value).matches(|val| val.eq(&"Hello")); } #[test] #[should_panic(expected = "\n\texpectation failed for value <\"Hello\">")] fn should_panic_if_value_does_not_match() { let value = "Hello"; assert_that(&value).matches(|val| val.eq(&"Hi")); } #[test] fn should_permit_chained_matches_calls() { let value = ("Hello", "World"); assert_that(&value) .matches(|val| val.0.eq("Hello")) .matches(|val| val.1.eq("World")); } #[test] fn should_be_able_to_map_to_inner_field_of_struct_when_matching() { let test_struct = TestStruct { value: 5 }; assert_that(&test_struct) .map(|val| &val.value) .is_equal_to(&5); } #[derive(Debug, PartialEq)] struct TestStruct { pub value: u8, } } speculoos-0.11.0/src/numeric.rs000064400000000000000000000207661046102023000145310ustar 00000000000000use super::{AssertionFailure, Spec}; use std::borrow::Borrow; use std::cmp::PartialOrd; use std::fmt::Debug; #[cfg(feature = "num")] use num::Float; pub trait OrderedAssertions where T: Debug + PartialOrd, { fn is_less_than>(&mut self, other: E); fn is_less_than_or_equal_to>(&mut self, other: E); fn is_greater_than>(&mut self, other: E); fn is_greater_than_or_equal_to>(&mut self, other: E); } impl<'s, T> OrderedAssertions for Spec<'s, T> where T: Debug + PartialOrd, { /// Asserts that the subject is less than the expected value. The subject type must /// implement `PartialOrd`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&1).is_less_than(&2); /// ``` fn is_less_than>(&mut self, other: E) { let subject = self.subject; let borrowed_other = other.borrow(); if subject >= borrowed_other { AssertionFailure::from_spec(self) .with_expected(format!("value less than <{:?}>", borrowed_other)) .with_actual(format!("<{:?}>", subject)) .fail(); } } /// Asserts that the subject is less than or equal to the expected value. The subject type /// must implement `PartialOrd`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&2).is_less_than_or_equal_to(&2); /// ``` fn is_less_than_or_equal_to>(&mut self, other: E) { let subject = self.subject; let borrowed_other = other.borrow(); if subject > borrowed_other { AssertionFailure::from_spec(self) .with_expected(format!( "value less than or equal to <{:?}>", borrowed_other )) .with_actual(format!("<{:?}>", subject)) .fail(); } } /// Asserts that the subject is greater than the expected value. The subject type must /// implement `PartialOrd`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&2).is_greater_than(&1); /// ``` fn is_greater_than>(&mut self, other: E) { let subject = self.subject; let borrowed_other = other.borrow(); if subject <= borrowed_other { AssertionFailure::from_spec(self) .with_expected(format!("value greater than <{:?}>", borrowed_other)) .with_actual(format!("<{:?}>", subject)) .fail(); } } /// Asserts that the subject is greater than or equal to the expected value. The subject type /// must implement `PartialOrd`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&2).is_greater_than_or_equal_to(&1); /// ``` fn is_greater_than_or_equal_to>(&mut self, other: E) { let subject = self.subject; let borrowed_other = other.borrow(); if subject < borrowed_other { AssertionFailure::from_spec(self) .with_expected(format!( "value greater than or equal to <{:?}>", borrowed_other )) .with_actual(format!("<{:?}>", subject)) .fail(); } } } #[cfg(feature = "num")] pub trait FloatAssertions { fn is_close_to, O: Borrow>(&mut self, expected: E, tolerance: O); } #[cfg(feature = "num")] impl<'s, T: Float + Debug> FloatAssertions for Spec<'s, T> { /// Asserts that the subject is close to the expected value by the specified tolerance. /// The subject type must implement `Float` and `Debug`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&2.0f64).is_close_to(2.0f64, 0.01f64); /// ``` fn is_close_to, O: Borrow>(&mut self, expected: E, tolerance: O) { let subject = *self.subject; let borrowed_expected = expected.borrow(); let borrowed_tolerance = tolerance.borrow(); let difference = (subject - *borrowed_expected).abs(); if !subject.is_finite() || difference > borrowed_tolerance.abs() { AssertionFailure::from_spec(self) .with_expected(format!( "float close to <{:?}> (tolerance of <{:?}>)", borrowed_expected, borrowed_tolerance )) .with_actual(format!("<{:?}>", subject)) .fail(); } } } #[cfg(test)] mod tests { use super::super::prelude::*; use num::Float; #[test] fn is_less_than_should_allow_multiple_borrow_forms() { assert_that(&1).is_less_than(2); assert_that(&1).is_less_than(&mut 2); assert_that(&1).is_less_than(&2); } #[test] fn should_not_panic_if_value_is_less_than_expected() { assert_that(&1).is_less_than(&2); } #[test] #[should_panic(expected = "\n\texpected: value less than <2>\n\t but was: <3>")] fn should_panic_if_value_is_greater_than_expected() { assert_that(&3).is_less_than(&2); } #[test] fn is_less_than_or_equal_to_should_allow_multiple_borrow_forms() { assert_that(&2).is_less_than_or_equal_to(2); assert_that(&2).is_less_than_or_equal_to(&mut 2); assert_that(&2).is_less_than_or_equal_to(&2); } #[test] fn should_not_panic_if_value_is_less_than_or_equal_to_than_expected() { assert_that(&2).is_less_than_or_equal_to(&2); assert_that(&2).is_less_than_or_equal_to(&3); } #[test] #[should_panic(expected = "\n\texpected: value less than or equal to <2>\n\t but was: <3>")] fn should_panic_if_value_is_greater_than_or_not_equal_to_expected() { assert_that(&3).is_less_than_or_equal_to(&2); } #[test] fn is_greater_than_should_allow_multiple_borrow_forms() { assert_that(&3).is_greater_than(2); assert_that(&3).is_greater_than(&mut 2); assert_that(&3).is_greater_than(&2); } #[test] fn should_not_panic_if_value_is_greater_than_expected() { assert_that(&3).is_greater_than(&2); } #[test] #[should_panic(expected = "\n\texpected: value greater than <3>\n\t but was: <2>")] fn should_panic_if_value_is_less_than_expected() { assert_that(&2).is_greater_than(&3); } #[test] fn is_greater_than_or_equal_to_should_allow_multiple_borrow_forms() { assert_that(&3).is_greater_than_or_equal_to(3); assert_that(&3).is_greater_than_or_equal_to(&mut 3); assert_that(&3).is_greater_than_or_equal_to(&3); } #[test] fn should_not_panic_if_value_is_greater_than_or_equal_to_expected() { assert_that(&3).is_greater_than_or_equal_to(&3); assert_that(&3).is_greater_than_or_equal_to(&2); } #[test] #[should_panic(expected = "\n\texpected: value greater than or equal to <3>\n\t but was: <2>")] fn should_panic_if_value_is_less_than_or_not_equal_to_expected() { assert_that(&2).is_greater_than_or_equal_to(&3); } #[test] fn is_close_to_should_allow_multiple_borrow_forms() { assert_that(&2.0f64).is_close_to(2.0f64, 0.01f64); assert_that(&2.0f64).is_close_to(&mut 2.0f64, 0.01f64); assert_that(&2.0f64).is_close_to(&2.0f64, 0.01f64); } #[test] fn should_not_panic_if_float_exactly_matches() { assert_that(&2.0f64).is_close_to(2.0f64, 0.01f64); assert_that(&0f64).is_close_to(0f64, 0.01f64); } #[test] fn should_not_panic_if_float_is_close_to() { assert_that(&1e-40f32).is_close_to(0.0f32, 0.1f32); } #[test] #[should_panic(expected = " expected: float close to <1.0> (tolerance of <0.01>) but was: <2.0>")] fn should_panic_if_float_is_not_close_to() { assert_that(&2.0f64).is_close_to(1.0f64, 0.01f64); } #[test] #[should_panic(expected = " expected: float close to <1.0> (tolerance of <0.01>) but was: ")] fn should_panic_if_float_is_nan() { assert_that(&Float::nan()).is_close_to(1.0f64, 0.01f64); } #[test] #[should_panic(expected = " expected: float close to <1.0> (tolerance of <0.01>) but was: ")] fn should_panic_if_float_is_infinity() { assert_that(&Float::infinity()).is_close_to(1.0f64, 0.01f64); } #[test] #[should_panic(expected = " expected: float close to <1.0> (tolerance of <0.01>) but was: <-inf>")] fn should_panic_if_float_is_negative_infinity() { assert_that(&Float::neg_infinity()).is_close_to(1.0f64, 0.01f64); } } speculoos-0.11.0/src/option.rs000064400000000000000000000122151046102023000143650ustar 00000000000000use super::{AssertionFailure, Spec}; use std::borrow::Borrow; use std::cmp::PartialEq; use std::fmt::Debug; pub trait OptionAssertions<'r, T> where T: Debug, { fn is_some(&mut self) -> Spec<'r, T>; fn is_none(&mut self); } pub trait ContainingOptionAssertions where T: Debug + PartialEq, { fn contains_value>(&mut self, expected_value: E); } impl<'s, T> ContainingOptionAssertions for Spec<'s, Option> where T: Debug + PartialEq, { /// Asserts that the subject is a `Some` containing the expected value. The subject type must /// be an `Option`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&Some(1)).contains_value(&1); /// ``` fn contains_value>(&mut self, expected_value: E) { let borrowed_expected_value = expected_value.borrow(); match *self.subject { Some(ref val) => { if !val.eq(borrowed_expected_value) { AssertionFailure::from_spec(self) .with_expected(format!("option to contain <{:?}>", borrowed_expected_value)) .with_actual(format!("<{:?}>", val)) .fail(); } } None => { AssertionFailure::from_spec(self) .with_expected(format!("option<{:?}>", borrowed_expected_value)) .with_actual("option[none]".to_string()) .fail(); } }; } } impl<'s, T> OptionAssertions<'s, T> for Spec<'s, Option> where T: Debug, { /// Asserts that the subject is `Some`. The subject type must be an `Option`. /// /// This will return a new `Spec` containing the unwrapped value if it is `Some`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&Some(1)).is_some(); /// ``` fn is_some(&mut self) -> Spec<'s, T> { match *self.subject { Some(ref val) => Spec { subject: val, subject_name: self.subject_name, location: self.location.clone(), description: self.description, }, None => { AssertionFailure::from_spec(self) .with_expected("option[some]".to_string()) .with_actual("option[none]".to_string()) .fail(); unreachable!(); } } } /// Asserts that the subject is `None`. The value type must be an `Option`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&Option::None::).is_none(); /// ``` fn is_none(&mut self) { match *self.subject { None => (), Some(ref val) => { AssertionFailure::from_spec(self) .with_expected("option[none]".to_string()) .with_actual(format!("option<{:?}>", val)) .fail(); } } } } #[cfg(test)] mod tests { use super::super::prelude::*; #[test] fn should_not_panic_if_option_is_expected_to_contain_value_and_does() { let option = Some("Hello"); assert_that(&option).is_some(); } #[test] #[should_panic(expected = "\n\texpected: option[some]\n\t but was: option[none]")] fn should_panic_if_option_is_expected_to_contain_value_and_does_not() { let option: Option<&str> = None; assert_that(&option).is_some(); } #[test] fn should_be_able_to_unwrap_option_if_some() { let option = Some("Hello"); assert_that(&option).is_some().is_equal_to(&"Hello"); } #[test] fn should_be_able_to_unwrap_referenced_vec() { assert_that(&Some(&vec![1, 2, 3])).is_some().has_length(3); } #[test] fn contains_value_should_allow_multiple_borrow_types() { let option = Some("Hello"); assert_that(&option).contains_value("Hello"); assert_that(&option).contains_value(&mut "Hello"); assert_that(&option).contains_value(&"Hello"); } #[test] fn should_not_panic_if_option_contains_expected_value() { let option = Some("Hello"); assert_that(&option).contains_value(&"Hello"); } #[test] #[should_panic(expected = "\n\texpected: option to contain <\"Hi\">\n\t but was: <\"Hello\">")] fn should_panic_if_option_does_not_contain_expected_value() { let option = Some("Hello"); assert_that(&option).contains_value(&"Hi"); } #[test] #[should_panic(expected = "\n\texpected: option<\"Hello\">\n\t but was: option[none]")] fn should_panic_if_option_is_none_but_expected_value() { let option: Option<&str> = None; assert_that(&option).contains_value(&"Hello"); } #[test] fn should_not_panic_if_option_is_empty() { let option: Option<&str> = None; assert_that(&option).is_none(); } #[test] #[should_panic(expected = "\n\texpected: option[none]\n\t but was: option<\"Hello\"")] fn should_panic_if_option_is_not_empty_but_was_expected_as_empty() { let option = Some("Hello"); assert_that(&option).is_none(); } } speculoos-0.11.0/src/path.rs000064400000000000000000000253671046102023000140250ustar 00000000000000use super::{AssertionFailure, DescriptiveSpec, Spec}; use std::borrow::Borrow; use std::path::Path; pub trait PathAssertions { fn exists(&mut self); fn does_not_exist(&mut self); fn is_a_file(&mut self); fn is_a_directory(&mut self); fn has_file_name<'r, E: Borrow<&'r str>>(&mut self, expected_file_name: E); } impl<'s, T> PathAssertions for Spec<'s, T> where T: AsRef, { /// Asserts that the subject `Path` refers to an existing location. /// /// ```rust, ignore /// assert_that(&Path::new("/tmp/file")).exists(); /// ``` fn exists(&mut self) { exists(self.subject.as_ref(), self) } /// Asserts that the subject `Path` does not refer to an existing location. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::path::Path; /// assert_that(&Path::new("/tmp/file")).does_not_exist(); /// ``` fn does_not_exist(&mut self) { does_not_exist(self.subject.as_ref(), self) } /// Asserts that the subject `Path` refers to an existing file. /// /// ```rust, ignore /// assert_that(&Path::new("/tmp/file")).is_a_file(); /// ``` fn is_a_file(&mut self) { is_a_file(self.subject.as_ref(), self) } /// Asserts that the subject `Path` refers to an existing directory. /// /// ```rust, ignore /// assert_that(&Path::new("/tmp/dir/")).is_a_directory(); /// ``` fn is_a_directory(&mut self) { is_a_directory(self.subject.as_ref(), self) } /// Asserts that the subject `Path` has the expected file name. /// /// ```rust /// # use speculoos::prelude::*; /// # use std::path::Path; /// assert_that(&Path::new("/tmp/file")).has_file_name(&"file"); /// ``` fn has_file_name<'r, E: Borrow<&'r str>>(&mut self, expected_file_name: E) { has_file_name(self.subject.as_ref(), expected_file_name.borrow(), self) } } fn exists<'s, S: DescriptiveSpec<'s>>(subject: &Path, spec: &'s S) { if !subject.exists() { AssertionFailure::from_spec(spec) .with_expected(format!("Path of <{:?}> to exist", subject)) .with_actual("a non-existent Path".to_string()) .fail(); } } fn does_not_exist<'s, S: DescriptiveSpec<'s>>(subject: &Path, spec: &'s S) { if subject.exists() { AssertionFailure::from_spec(spec) .with_expected(format!("Path of <{:?}> to not exist", subject)) .with_actual("a resolvable Path".to_string()) .fail(); } } fn is_a_file<'s, S: DescriptiveSpec<'s>>(subject: &Path, spec: &'s S) { if !subject.is_file() { AssertionFailure::from_spec(spec) .with_expected(format!("Path of <{:?}> to be a file", subject)) .with_actual("not a resolvable file".to_string()) .fail(); } } fn is_a_directory<'s, S: DescriptiveSpec<'s>>(subject: &Path, spec: &'s S) { if !subject.is_dir() { AssertionFailure::from_spec(spec) .with_expected(format!("Path of <{:?}> to be a directory", subject)) .with_actual("not a resolvable directory".to_string()) .fail(); } } fn has_file_name<'s, S: DescriptiveSpec<'s>>( subject: &Path, expected_file_name: &str, spec: &'s S, ) { let subject_file_name = match subject.file_name() { Some(os_string) => match os_string.to_str() { Some(val) => val, None => { fail_from_file_name( spec, expected_file_name, "an invalid UTF-8 file name".to_string(), ); unreachable!(); } }, None => { fail_from_file_name( spec, expected_file_name, format!("a non-resolvable path <{:?}>", subject), ); unreachable!(); } }; if !subject_file_name.eq(expected_file_name) { fail_from_file_name(spec, expected_file_name, format!("<{}>", subject_file_name)); } } fn fail_from_file_name<'s, S: DescriptiveSpec<'s>>(spec: &'s S, expected: &str, actual: String) { AssertionFailure::from_spec(spec) .with_expected(build_file_name_message(expected)) .with_actual(actual) .fail(); } fn build_file_name_message(file_name: &str) -> String { format!("Path with file name of <{}>", file_name) } #[cfg(test)] mod tests { use super::super::prelude::*; use std::path::{Path, PathBuf}; static MANIFEST_PATH: &str = env!("CARGO_MANIFEST_DIR"); #[test] fn should_accept_any_path_reference() { assert_that(&Path::new(MANIFEST_PATH)).exists(); assert_that(&PathBuf::from(MANIFEST_PATH)).exists(); assert_that(&MANIFEST_PATH).exists(); } #[test] pub fn should_not_panic_if_path_exists() { assert_that(&Path::new(MANIFEST_PATH)).exists(); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_path_does_not_exist() { let failing_path = MANIFEST_PATH.to_string() + "/does-not-exist"; assert_that(&Path::new(&failing_path)).exists(); } #[test] pub fn should_not_panic_if_path_represents_a_directory() { assert_that(&Path::new(MANIFEST_PATH)).is_a_directory(); } #[test] pub fn should_not_panic_if_path_does_not_exist_when_expected() { let failing_path = MANIFEST_PATH.to_string() + "/does-not-exist"; assert_that(&Path::new(&failing_path)).does_not_exist(); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_path_exists_when_not_expected() { assert_that(&Path::new(MANIFEST_PATH)).does_not_exist(); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_path_does_not_represent_a_directory() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&Path::new(&path)).is_a_directory(); } #[test] pub fn should_not_panic_if_path_represents_a_file() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&Path::new(&path)).is_a_file(); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_path_does_not_represent_a_file() { assert_that(&Path::new(&MANIFEST_PATH)).is_a_file(); } #[test] pub fn has_file_name_should_allow_multiple_borrow_forms_for_path() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&Path::new(&path)).has_file_name("Cargo.toml"); assert_that(&Path::new(&path)).has_file_name(&mut "Cargo.toml"); assert_that(&Path::new(&path)).has_file_name(&"Cargo.toml"); } #[test] pub fn should_not_panic_if_path_has_correct_file_name() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&Path::new(&path)).has_file_name(&"Cargo.toml"); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_path_does_not_have_correct_file_name() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&Path::new(&path)).has_file_name(&"pom.xml"); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_path_does_not_have_a_file_name() { let path = MANIFEST_PATH.to_string() + "/.."; assert_that(&Path::new(&path)).has_file_name(&"pom.xml"); } #[test] pub fn should_not_panic_if_pathbuf_exists() { assert_that(&PathBuf::from(MANIFEST_PATH)).exists(); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_pathbuf_does_not_exist() { let failing_path = MANIFEST_PATH.to_string() + "/does-not-exist"; assert_that(&PathBuf::from(&failing_path)).exists(); } #[test] pub fn should_not_panic_if_pathbuf_represents_a_directory() { assert_that(&PathBuf::from(MANIFEST_PATH)).is_a_directory(); } #[test] pub fn should_not_panic_if_pathbuf_does_not_exist_when_expected() { let failing_path = MANIFEST_PATH.to_string() + "/does-not-exist"; assert_that(&PathBuf::from(&failing_path)).does_not_exist(); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_pathbuf_exists_when_not_expected() { assert_that(&PathBuf::from(MANIFEST_PATH)).does_not_exist(); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_pathbuf_does_not_represent_a_directory() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&PathBuf::from(&path)).is_a_directory(); } #[test] pub fn should_not_panic_if_pathbuf_represents_a_file() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&PathBuf::from(&path)).is_a_file(); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_pathbuf_does_not_represent_a_file() { assert_that(&PathBuf::from(&MANIFEST_PATH)).is_a_file(); } #[test] pub fn has_file_name_should_allow_multiple_borrow_forms_for_pathbuf() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&PathBuf::from(&path)).has_file_name("Cargo.toml"); assert_that(&PathBuf::from(&path)).has_file_name(&mut "Cargo.toml"); assert_that(&PathBuf::from(&path)).has_file_name(&"Cargo.toml"); } #[test] pub fn should_not_panic_if_pathbuf_has_correct_file_name() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&PathBuf::from(&path)).has_file_name(&"Cargo.toml"); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_pathbuf_does_not_have_correct_file_name() { let path = MANIFEST_PATH.to_string() + "/Cargo.toml"; assert_that(&PathBuf::from(&path)).has_file_name(&"pom.xml"); } #[test] // It's unfortunately a bit hard to expect a message without knowing the manifest path #[should_panic] pub fn should_panic_if_pathbuf_does_not_have_a_file_name() { let path = MANIFEST_PATH.to_string() + "/.."; assert_that(&PathBuf::from(&path)).has_file_name(&"pom.xml"); } } speculoos-0.11.0/src/prelude.rs000064400000000000000000000012641046102023000145170ustar 00000000000000pub use super::boolean::BooleanAssertions; pub use super::hashmap::{EntryHashMapAssertions, HashMapAssertions, KeyHashMapAssertions}; pub use super::hashset::HashSetAssertions; pub use super::iter::{ ContainingIntoIterAssertions, ContainingIteratorAssertions, MappingIterAssertions, }; pub use super::numeric::OrderedAssertions; pub use super::option::{ContainingOptionAssertions, OptionAssertions}; pub use super::path::PathAssertions; pub use super::result::{ContainingResultAssertions, ResultAssertions}; pub use super::string::StrAssertions; pub use super::vec::VecAssertions; pub use super::{assert_that, asserting}; #[cfg(feature = "num")] pub use super::numeric::FloatAssertions; speculoos-0.11.0/src/result.rs000064400000000000000000000220231046102023000143710ustar 00000000000000use super::{AssertionFailure, Spec}; use std::borrow::Borrow; use std::fmt::Debug; pub trait ResultAssertions<'s, T, E> where T: Debug, E: Debug, { fn is_ok(&mut self) -> Spec<'s, T>; fn is_err(&mut self) -> Spec<'s, E>; } pub trait ContainingResultAssertions where T: Debug, E: Debug, { fn is_ok_containing>(&mut self, expected_value: V) where T: PartialEq; fn is_err_containing>(&mut self, expected_value: V) where E: PartialEq; } impl<'s, T, E> ContainingResultAssertions for Spec<'s, Result> where T: Debug, E: Debug, { /// Asserts that the subject is an `Ok` Result containing the expected value. /// The subject type must be a `Result`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&Result::Ok::(1)).is_ok_containing(&1); /// ``` fn is_ok_containing>(&mut self, expected_value: V) where T: PartialEq, { let borrowed_expected_value = expected_value.borrow(); match *self.subject { Ok(ref val) => { if !val.eq(borrowed_expected_value) { AssertionFailure::from_spec(self) .with_expected(build_detail_message("ok", borrowed_expected_value)) .with_actual(build_detail_message("ok", val)) .fail(); } } Err(ref val) => { AssertionFailure::from_spec(self) .with_expected(build_detail_message("ok", borrowed_expected_value)) .with_actual(build_detail_message("err", val)) .fail(); } } } /// Asserts that the subject is an `Err` Result containing the expected value. /// The subject type must be a `Result`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&Result::Err::(1)).is_err_containing(&1); /// ``` fn is_err_containing>(&mut self, expected_value: V) where E: PartialEq, { let borrowed_expected_value = expected_value.borrow(); match *self.subject { Err(ref val) => { if !val.eq(borrowed_expected_value) { AssertionFailure::from_spec(self) .with_expected(build_detail_message("err", borrowed_expected_value)) .with_actual(build_detail_message("err", val)) .fail(); } } Ok(ref val) => { AssertionFailure::from_spec(self) .with_expected(build_detail_message("err", borrowed_expected_value)) .with_actual(build_detail_message("ok", val)) .fail(); } } } } fn build_detail_message(variant: &'static str, value: T) -> String { format!("Result[{}] containing <{:?}>", variant, value) } impl<'s, T, E> ResultAssertions<'s, T, E> for Spec<'s, Result> where T: Debug, E: Debug, { /// Asserts that the subject is `Ok`. The value type must be a `Result`. /// /// This will return a new `Spec` containing the unwrapped value if it is `Ok`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&Result::Ok::(1)).is_ok(); /// ``` fn is_ok(&mut self) -> Spec<'s, T> { match *self.subject { Ok(ref val) => Spec { subject: val, subject_name: self.subject_name, location: self.location.clone(), description: self.description, }, Err(ref err) => { AssertionFailure::from_spec(self) .with_expected("result[ok]".to_string()) .with_actual(format!("result[error]<{:?}>", err)) .fail(); unreachable!(); } } } /// Asserts that the subject is `Err`. The value type must be a `Result`. /// /// This will return a new `Spec` containing the unwrapped value if it is `Err`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&Result::Err::(1)).is_err(); /// ``` fn is_err(&mut self) -> Spec<'s, E> { match *self.subject { Err(ref val) => Spec { subject: val, subject_name: self.subject_name, location: self.location.clone(), description: self.description, }, Ok(ref val) => { AssertionFailure::from_spec(self) .with_expected("result[error]".to_string()) .with_actual(format!("result[ok]<{:?}>", val)) .fail(); unreachable!(); } } } } #[cfg(test)] mod tests { use super::super::prelude::*; #[test] fn should_not_panic_if_result_is_expected_to_be_ok_and_is() { let result: Result<&str, &str> = Ok("Hello"); assert_that(&result).is_ok(); } #[test] #[should_panic(expected = "\n\texpected: result[ok]\n\t but was: result[error]<\"Oh no\">")] fn should_panic_if_result_is_expected_to_be_ok_and_is_not() { let result: Result<&str, &str> = Err("Oh no"); assert_that(&result).is_ok(); } #[test] fn should_return_unwrapped_value_if_subject_is_ok() { let result: Result<&str, &str> = Ok("Hello"); assert_that(&result).is_ok().is_equal_to(&"Hello"); } #[test] fn should_not_panic_if_result_is_expected_to_be_error_and_is() { let result: Result<&str, &str> = Err("Oh no"); assert_that(&result).is_err(); } #[test] #[should_panic(expected = "\n\texpected: result[error]\n\t but was: result[ok]<\"Hello\">")] fn should_panic_if_result_is_expected_to_be_error_and_is_not() { let result: Result<&str, &str> = Ok("Hello"); assert_that(&result).is_err(); } #[test] fn should_return_unwrapped_value_if_subject_is_err() { let result: Result<&str, &str> = Err("Hello"); assert_that(&result).is_err().is_equal_to(&"Hello"); } #[test] fn is_ok_containing_should_allow_multiple_borrow_forms() { let result: Result<&str, &str> = Ok("Hello"); assert_that(&result).is_ok_containing("Hello"); assert_that(&result).is_ok_containing(&mut "Hello"); assert_that(&result).is_ok_containing(&"Hello"); } #[test] fn should_not_panic_if_result_is_ok_with_expected_value() { let result: Result<&str, &str> = Ok("Hello"); assert_that(&result).is_ok_containing(&"Hello"); } #[test] fn should_not_panic_if_result_is_ok_with_uncomparable_ok() { #[derive(Debug)] struct Incomparable; let result: Result<&str, Incomparable> = Ok("Hello"); assert_that(&result).is_ok_containing(&"Hello"); } #[test] #[should_panic(expected = "\n\texpected: Result[ok] containing <\"Hi\">\ \n\t but was: Result[ok] containing <\"Hello\">")] fn should_panic_if_result_is_ok_without_expected_value() { let result: Result<&str, &str> = Ok("Hello"); assert_that(&result).is_ok_containing(&"Hi"); } #[test] #[should_panic(expected = "\n\texpected: Result[ok] containing <\"Hi\">\ \n\t but was: Result[err] containing <\"Hi\">")] fn should_panic_if_result_is_err_if_ok_with_value_expected() { let result: Result<&str, &str> = Err("Hi"); assert_that(&result).is_ok_containing(&"Hi"); } #[test] fn is_error_containing_should_allow_multiple_borrow_forms() { let result: Result<&str, &str> = Err("Oh no"); assert_that(&result).is_err_containing("Oh no"); assert_that(&result).is_err_containing(&mut "Oh no"); assert_that(&result).is_err_containing(&"Oh no"); } #[test] fn should_not_panic_if_result_is_err_with_expected_value() { let result: Result<&str, &str> = Err("Oh no"); assert_that(&result).is_err_containing(&"Oh no"); } #[test] fn should_not_panic_if_result_is_err_with_uncomparable_ok() { #[derive(Debug)] struct Incomparable; let result: Result = Err("Oh no"); assert_that(&result).is_err_containing(&"Oh no"); } #[test] #[should_panic(expected = "\n\texpected: Result[err] containing <\"Oh no\">\ \n\t but was: Result[err] containing <\"Whoops\">")] fn should_panic_if_result_is_err_without_expected_value() { let result: Result<&str, &str> = Err("Whoops"); assert_that(&result).is_err_containing(&"Oh no"); } #[test] #[should_panic(expected = "\n\texpected: Result[err] containing <\"Oh no\">\ \n\t but was: Result[ok] containing <\"Oh no\">")] fn should_panic_if_result_is_ok_if_err_with_value_expected() { let result: Result<&str, &str> = Ok("Oh no"); assert_that(&result).is_err_containing(&"Oh no"); } } speculoos-0.11.0/src/string.rs000064400000000000000000000225631046102023000143720ustar 00000000000000use super::{AssertionFailure, DescriptiveSpec, Spec}; use std::borrow::Borrow; pub trait StrAssertions { fn starts_with>(&mut self, expected: E); fn ends_with>(&mut self, expected: E); fn contains>(&mut self, expected: E); fn does_not_contain>(&mut self, expected: E); fn is_empty(&mut self); } impl<'s, T> StrAssertions for Spec<'s, T> where T: AsRef, { /// Asserts that the subject `&str` starts with the provided `&str`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&"Hello").starts_with("H"); /// ``` fn starts_with>(&mut self, expected: E) { let subject = self.subject.as_ref(); starts_with(self, subject, expected.as_ref()); } /// Asserts that the subject `&str` ends with the provided `&str`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&"Hello").ends_with("o"); /// ``` fn ends_with>(&mut self, expected: E) { let subject = self.subject.as_ref(); ends_with(self, subject, expected.as_ref()); } /// Asserts that the subject `&str` contains the provided `&str`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&"Hello").contains("e"); /// ``` fn contains>(&mut self, expected: E) { let subject = self.subject.as_ref(); contains(self, subject, expected.as_ref()); } /// Asserts that the subject `&str` contains the provided `&str`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&"Hello").contains("e"); /// ``` fn does_not_contain>(&mut self, expected: E) { let subject = self.subject.as_ref(); does_not_contain(self, subject, expected.as_ref()); } /// Asserts that the subject `&str` is empty. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&"").is_empty(); /// ``` fn is_empty(&mut self) { let subject = self.subject.as_ref(); is_empty(self, subject); } } fn starts_with<'r, 's, S: DescriptiveSpec<'s>, E: Borrow<&'r str>>( spec: &'s S, subject: &str, expected: E, ) { let borrowed_expected = expected.borrow(); if !subject.starts_with(borrowed_expected) { AssertionFailure::from_spec(spec) .with_expected(format!("string starting with <{:?}>", borrowed_expected)) .with_actual(format!("<{:?}>", subject)) .fail(); } } fn ends_with<'r, 's, S: DescriptiveSpec<'s>, E: Borrow<&'r str>>( spec: &'s S, subject: &str, expected: E, ) { let borrowed_expected = expected.borrow(); if !subject.ends_with(borrowed_expected) { AssertionFailure::from_spec(spec) .with_expected(format!("string ending with <{:?}>", borrowed_expected)) .with_actual(format!("<{:?}>", subject)) .fail(); } } fn contains<'r, 's, S: DescriptiveSpec<'s>, E: Borrow<&'r str>>( spec: &'s S, subject: &str, expected: E, ) { let borrowed_expected = expected.borrow(); if !subject.contains(borrowed_expected) { AssertionFailure::from_spec(spec) .with_expected(format!("string containing <{:?}>", borrowed_expected)) .with_actual(format!("<{:?}>", subject)) .fail(); } } fn does_not_contain<'r, 's, S: DescriptiveSpec<'s>, E: Borrow<&'r str>>( spec: &'s S, subject: &str, expected: E, ) { let borrowed_expected = expected.borrow(); if subject.contains(borrowed_expected) { AssertionFailure::from_spec(spec) .with_expected(format!("string not containing <{:?}>", borrowed_expected)) .with_actual(format!("<{:?}>", subject)) .fail(); } } fn is_empty<'s, S: DescriptiveSpec<'s>>(spec: &'s S, subject: &str) { if !subject.is_empty() { AssertionFailure::from_spec(spec) .with_expected("an empty string".to_string()) .with_actual(format!("<{:?}>", subject)) .fail(); } } #[cfg(test)] mod tests { use super::super::prelude::*; use std::borrow::Cow; #[test] fn should_allow_multiple_borrow_forms_for_str() { let value = "Hello"; assert_that(&value).starts_with("H"); assert_that(&value).starts_with(&mut "H"); assert_that(&value).starts_with(&"H"); assert_that(&value).starts_with(Cow::from("H")); assert_that(&value).starts_with("H".to_string()); assert_that(&value).ends_with("o"); assert_that(&value).ends_with(&mut "o"); assert_that(&value).ends_with(&"o"); assert_that(&value).ends_with(Cow::from("o")); assert_that(&value).ends_with("o".to_string()); assert_that(&value).contains("l"); assert_that(&value).contains(&mut "l"); assert_that(&value).contains(&"l"); assert_that(&value).contains(Cow::from("l")); assert_that(&value).contains("l".to_string()); } #[test] fn should_not_panic_if_str_starts_with_value() { let value = "Hello"; assert_that(&value).starts_with("H"); } #[test] #[should_panic(expected = "\n\texpected: string starting with <\"A\">\ \n\t but was: <\"Hello\">")] fn should_panic_if_str_does_not_start_with_value() { let value = "Hello"; assert_that(&value).starts_with("A"); } #[test] fn should_not_panic_if_str_ends_with_value() { let value = "Hello"; assert_that(&value).ends_with("o"); } #[test] #[should_panic(expected = "\n\texpected: string ending with <\"A\">\n\t but was: <\"Hello\">")] fn should_panic_if_str_does_not_end_with_value() { let value = "Hello"; assert_that(&value).ends_with("A"); } #[test] fn should_not_panic_if_str_contains_value() { let value = "Hello"; assert_that(&value).contains("l"); } #[test] fn should_not_panic_if_str_does_not_contains_value() { let value = "Hello"; assert_that(&value).does_not_contain("x"); } #[test] #[should_panic( expected = "\n\texpected: string not containing <\"l\">\n\t but was: <\"Hello\">" )] fn should_panic_if_str_contains_value() { let value = "Hello"; assert_that(&value).does_not_contain("l"); } #[test] #[should_panic(expected = "\n\texpected: string containing <\"A\">\n\t but was: <\"Hello\">")] fn should_panic_if_str_does_not_contain_value() { let value = "Hello"; assert_that(&value).contains("A"); } #[test] fn should_not_panic_if_str_is_empty() { let value = ""; assert_that(&value).is_empty(); } #[test] #[should_panic(expected = "\n\texpected: an empty string\n\t but was: <\"Hello\">")] fn should_panic_if_str_is_not_empty() { let value = "Hello"; assert_that(&value).is_empty(); } #[test] fn should_allow_multiple_borrow_forms_for_string() { let value = "Hello".to_owned(); assert_that(&value).starts_with("H"); assert_that(&value).starts_with(&mut "H"); assert_that(&value).starts_with(&"H"); assert_that(&value).starts_with(Cow::from("H")); assert_that(&value).starts_with("H".to_string()); assert_that(&value).ends_with("o"); assert_that(&value).ends_with(&mut "o"); assert_that(&value).ends_with(&"o"); assert_that(&value).ends_with(Cow::from("o")); assert_that(&value).ends_with("o".to_string()); assert_that(&value).contains("l"); assert_that(&value).contains(&mut "l"); assert_that(&value).contains(&"l"); assert_that(&value).contains(Cow::from("l")); assert_that(&value).contains("l".to_string()); } #[test] fn should_not_panic_if_string_starts_with_value() { let value = "Hello".to_owned(); assert_that(&value).starts_with("H"); } #[test] #[should_panic(expected = "\n\texpected: string starting with <\"A\">\ \n\t but was: <\"Hello\">")] fn should_panic_if_string_does_not_start_with_value() { let value = "Hello".to_owned(); assert_that(&value).starts_with("A"); } #[test] fn should_not_panic_if_string_ends_with_value() { let value = "Hello".to_owned(); assert_that(&value).ends_with("o"); } #[test] #[should_panic(expected = "\n\texpected: string ending with <\"A\">\n\t but was: <\"Hello\">")] fn should_panic_if_string_does_not_end_with_value() { let value = "Hello".to_owned(); assert_that(&value).ends_with("A"); } #[test] fn should_not_panic_if_string_contains_value() { let value = "Hello".to_owned(); assert_that(&value).contains("l"); } #[test] #[should_panic(expected = "\n\texpected: string containing <\"A\">\n\t but was: <\"Hello\">")] fn should_panic_if_string_does_not_contain_value() { let value = "Hello".to_owned(); assert_that(&value).contains("A"); } #[test] fn should_not_panic_if_string_is_empty() { let value = "".to_owned(); assert_that(&value).is_empty(); } #[test] #[should_panic(expected = "\n\texpected: an empty string\n\t but was: <\"Hello\">")] fn should_panic_if_string_is_not_empty() { let value = "Hello".to_owned(); assert_that(&value).is_empty(); } } speculoos-0.11.0/src/vec.rs000064400000000000000000000115031046102023000136310ustar 00000000000000use super::{AssertionFailure, Spec}; pub trait VecAssertions { fn has_length(&mut self, expected: usize); fn is_empty(&mut self); fn is_not_empty(&mut self); } impl<'s, T> VecAssertions for Spec<'s, Vec> { /// Asserts that the length of the subject vector is equal to the provided length. The subject /// type must be of `Vec`. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that!(vec![1, 2, 3, 4]).has_length(4); /// ``` fn has_length(&mut self, expected: usize) { let length = self.subject.len(); if length != expected { AssertionFailure::from_spec(self) .with_expected(format!("vec to have length <{}>", expected)) .with_actual(format!("<{}>", length)) .fail(); } } /// Asserts that the subject vector is empty. The subject type must be of `Vec`. /// /// ```rust /// # use speculoos::prelude::*; /// let test_vec: Vec = vec![]; /// assert_that!(test_vec).is_empty(); /// ``` fn is_empty(&mut self) { let subject = self.subject; if !subject.is_empty() { AssertionFailure::from_spec(self) .with_expected("an empty vec".to_string()) .with_actual(format!("a vec with length <{:?}>", subject.len())) .fail(); } } /// Asserts that the subject vector is not empty. The subject type must be of `Vec`. /// /// ```rust /// # use speculoos::prelude::*; /// let test_vec: Vec = vec![1]; /// assert_that!(test_vec).is_not_empty(); /// ``` fn is_not_empty(&mut self) { let subject = self.subject; if subject.is_empty() { AssertionFailure::from_spec(self) .with_expected("an non empty vec".to_string()) .with_actual(format!("a vec with length {}", subject.len())) .fail(); } } } impl<'s, T> VecAssertions for Spec<'s, &'s Vec> { /// Asserts that the length of the subject vector is equal to the provided length. The subject /// type must be of `&Vec` with a matching lifetime. /// /// ```rust /// # use speculoos::prelude::*; /// assert_that(&&vec![1, 2, 3, 4]).has_length(4); /// ``` fn has_length(&mut self, expected: usize) { let length = self.subject.len(); if length != expected { AssertionFailure::from_spec(self) .with_expected(format!("vec to have length <{}>", expected)) .with_actual(format!("<{}>", length)) .fail(); } } /// Asserts that the subject vector is empty. The subject type must be of `&Vec` with a /// matching lifetime. /// /// ```rust /// # use speculoos::prelude::*; /// let test_vec: &Vec = &vec![]; /// assert_that(&test_vec).is_empty(); /// ``` fn is_empty(&mut self) { let subject = self.subject; if !subject.is_empty() { AssertionFailure::from_spec(self) .with_expected("an empty vec".to_string()) .with_actual(format!("a vec with length <{:?}>", subject.len())) .fail(); } } /// Asserts that the subject vector is not empty. The subject type must be of `&Vec` with a /// matching lifetime. /// ```rust /// # use speculoos::prelude::*; /// let test_vec: Vec = vec![1]; /// assert_that(&test_vec).is_not_empty(); /// ``` fn is_not_empty(&mut self) { let subject = self.subject; if subject.is_empty() { AssertionFailure::from_spec(self) .with_expected("an non empty vec".to_string()) .with_actual(format!("a vec with length {}", subject.len())) .fail(); } } } #[cfg(test)] mod tests { use super::super::prelude::*; #[test] fn should_not_panic_if_vec_length_matches_expected() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec).has_length(3); assert_that(&&test_vec).has_length(3); } #[test] #[should_panic(expected = "\n\texpected: vec to have length <1>\n\t but was: <3>")] fn should_panic_if_vec_length_does_not_match_expected() { let test_vec = vec![1, 2, 3]; assert_that(&test_vec).has_length(1); assert_that(&&test_vec).has_length(1); } #[test] fn should_not_panic_if_vec_was_expected_to_be_empty_and_is() { let test_vec: Vec = vec![]; assert_that(&test_vec).is_empty(); assert_that(&&test_vec).is_empty(); } #[test] #[should_panic(expected = "\n\texpected: an empty vec\ \n\t but was: a vec with length <1>")] fn should_panic_if_vec_was_expected_to_be_empty_and_is_not() { assert_that(&vec![1]).is_empty(); assert_that(&&vec![1]).is_empty(); } }