Skip to content

Commit

Permalink
Implement an assertor for anyhow error (#115)
Browse files Browse the repository at this point in the history
* Implement an assertor for anyhow error

* Update documentation
  • Loading branch information
cocuh authored Jun 9, 2024
1 parent b35d288 commit 0554278
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 12 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ categories = ["development-tools::testing", "development-tools::debugging"]

[dependencies]
num-traits = { version = "0.2.15", optional = true }
anyhow = { version = "1.0.86", optional = true }

[dev-dependencies]
test-case = "3.1.0"

[features]
default = ["float"]
float = ["num-traits"]
float = ["dep:num-traits"]
testing = []

anyhow = ["dep:anyhow"]
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,31 @@ fn test_it() {
}
```

## anyhow

Supports asserting error value of `anyhow` under `anyhow` feature flag.

```toml
[dependencies]
assertor = { version = "*", features = ["anyhow"] }
```

```rust
use assertor::*;
use anyhow::anyhow;

fn anyhow_func() -> anyhow::Result<()> {
Err(anyhow!("failed to parse something in foobar"))
}

fn test_it() {
assert_that!(anyhow_func()).err().has_message("failed to parse something in foobar");
assert_that!(anyhow_func()).err().as_string().contains("parse something");
assert_that!(anyhow_func()).err().as_string().starts_with("failed");
assert_that!(anyhow_func()).err().as_string().ends_with("foobar");
}
```

## Feature ideas

- [ ] Color / Bold
Expand Down
118 changes: 118 additions & 0 deletions src/assertions/anyhow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2024 Google LLC
//
// 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.


use crate::base::{AssertionApi, AssertionResult, AssertionStrategy, Subject};
use crate::StringAssertion;

/// Trait for anyhow error assertion.
///
/// # Example
///
/// ```rust
/// use assertor::*;
/// use anyhow::anyhow;
///
/// fn anyhow_func() -> anyhow::Result<()> {
/// Err(anyhow!("failed to parse something in foobar"))
/// }
///
/// fn test_it() {
/// assert_that!(anyhow_func()).err().has_message("failed to parse something in foobar");
/// assert_that!(anyhow_func()).err().as_string().contains("parse something");
/// assert_that!(anyhow_func()).err().as_string().starts_with("failed");
/// assert_that!(anyhow_func()).err().as_string().ends_with("foobar");
/// }
/// ```
pub trait AnyhowErrorAssertion<R> {
/// Returns a new `String` subject which is the message of the error.
///
/// Related: [`StringAssertion`](crate::StringAssertion)
///
/// ```
/// use assertor::*;
/// use anyhow::anyhow;
///
/// assert_that!(anyhow!("error message")).as_string().is_same_string_to("error message");
/// assert_that!(anyhow!("error message")).as_string().contains("error");
///
///
/// fn some_func() -> anyhow::Result<()> {
/// Err(anyhow!("error message"))
/// }
/// assert_that!(some_func()).err().as_string().starts_with("error");
/// assert_that!(some_func()).err().as_string().ends_with("message");
/// ```
fn as_string(&self) -> Subject<String, (), R>;

/// Checks that the error message contains `expected`.
/// ```
/// use assertor::*;
/// use anyhow::anyhow;
///
/// assert_that!(anyhow!("error message")).has_message("error message");
///
/// fn some_func() -> anyhow::Result<()> {
/// Err(anyhow!("error message"))
/// }
/// assert_that!(some_func()).err().has_message("error message")
/// ```
fn has_message<E: Into<String>>(&self, expected: E) -> R;
}

impl<R> AnyhowErrorAssertion<R> for Subject<'_, anyhow::Error, (), R>
where
AssertionResult: AssertionStrategy<R>,
{
fn as_string(&self) -> Subject<String, (), R> {
let message = self.actual().to_string();
self.new_owned_subject(message, Some(format!("{}.to_string()", self.description_or_expr())), ())
}

fn has_message<E: Into<String>>(&self, expected: E) -> R {
self.as_string().is_same_string_to(expected)
}
}

#[cfg(test)]
mod tests {
use crate::testing::*;

use super::*;

#[test]
fn as_string() {
assert_that!(anyhow::Error::msg("error message")).as_string().is_same_string_to("error message");
assert_that!(anyhow::Error::msg("error message")).as_string().starts_with("error");
assert_that!(anyhow::Error::msg("error message")).as_string().contains("or");

assert_that!(check_that!(anyhow::Error::msg("error message")).as_string().is_same_string_to("wrong")).facts_are(
vec![
Fact::new("expected", "\"wrong\""),
Fact::new("actual", "\"error message\""),
]
);
}

#[test]
fn has_message() {
assert_that!(anyhow::Error::msg("error message")).has_message("error message");
assert_that!(check_that!(anyhow::Error::msg("error message")).has_message("wrong")).facts_are(
vec![
Fact::new("expected", "\"wrong\""),
Fact::new("actual", "\"error message\""),
]
);
}
}
3 changes: 3 additions & 0 deletions src/assertions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@ pub mod vec;
#[cfg(feature = "float")]
pub mod float;

#[cfg(feature = "anyhow")]
pub mod anyhow;

#[cfg(any(test, doc, feature = "testing"))]
pub(crate) mod testing;
60 changes: 50 additions & 10 deletions src/assertions/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use std::borrow::Borrow;
use std::fmt::Debug;

use crate::assert_that;
use crate::base::{AssertionApi, AssertionResult, AssertionStrategy, Subject};

/// Trait for result assertion.
Expand All @@ -40,18 +41,24 @@ pub trait ResultAssertion<R, OK, ERR> {

/// Checks that the subject is [`Result::Ok(expected)`](`std::result::Result::Err`).
fn has_ok<B: Borrow<OK>>(&self, expected: B) -> R
where
OK: PartialEq;
where
OK: PartialEq;

/// Checks that the subject is [`Result::Err(expected)`](`std::result::Result::Err`).
fn has_err<B: Borrow<ERR>>(&self, expected: B) -> R
where
ERR: PartialEq;
where
ERR: PartialEq;

/// Returns a new subject which is the ok value of the subject if the subject has ok value. Otherwise, it fails.
fn ok(&self) -> Subject<OK, (), R>;

/// Returns a new subject which is the error value of the subject if the subject has error value. Otherwise, it fails.
fn err(&self) -> Subject<ERR, (), R>;
}

impl<R, OK: Debug, ERR: Debug> ResultAssertion<R, OK, ERR> for Subject<'_, Result<OK, ERR>, (), R>
where
AssertionResult: AssertionStrategy<R>,
where
AssertionResult: AssertionStrategy<R>,
{
fn is_ok(&self) -> R {
if self.actual().is_ok() {
Expand Down Expand Up @@ -80,8 +87,8 @@ where
}

fn has_ok<B: Borrow<OK>>(&self, expected: B) -> R
where
OK: PartialEq,
where
OK: PartialEq,
{
match self.actual() {
Ok(actual) if actual.eq(expected.borrow()) => self.new_result().do_ok(),
Expand All @@ -99,8 +106,8 @@ where
}

fn has_err<B: Borrow<ERR>>(&self, expected: B) -> R
where
ERR: PartialEq,
where
ERR: PartialEq,
{
match self.actual() {
Err(actual) if actual.eq(expected.borrow()) => self.new_result().do_ok(),
Expand All @@ -116,10 +123,21 @@ where
.do_fail(),
}
}

fn ok(&self) -> Subject<OK, (), R> {
assert_that!(*self.actual()).is_ok();
self.new_subject(self.actual().as_ref().ok().unwrap(), Some(format!("{}.ok", self.description_or_expr())), ())
}

fn err(&self) -> Subject<ERR, (), R> {
assert_that!(*self.actual()).is_err();
self.new_subject(self.actual().as_ref().err().unwrap(), Some(format!("{}.err", self.description_or_expr())), ())
}
}

#[cfg(test)]
mod tests {
use crate::ComparableAssertion;
use crate::testing::*;

use super::*;
Expand Down Expand Up @@ -189,4 +207,26 @@ mod tests {
],
);
}

#[test]
fn ok() {
assert_that!(Result::<f64,()>::Ok(0.)).ok().is_at_most(1.);
}

#[test]
#[should_panic]
fn ok_panic() {
assert_that!(Result::<f64,()>::Err(())).ok().is_at_most(1.);
}

#[test]
fn err() {
assert_that!(Result::<(), f64>::Err(0.)).err().is_at_most(1.);
}

#[test]
#[should_panic]
fn err_panic() {
assert_that!(Result::<(), f64>::Ok(())).err().is_at_most(1.);
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
#[cfg(feature = "float")]
extern crate num_traits;

#[cfg(feature = "anyhow")]
pub use assertions::anyhow::AnyhowErrorAssertion;
pub use assertions::basic::{ComparableAssertion, EqualityAssertion};
pub use assertions::boolean::BooleanAssertion;
#[cfg(feature = "float")]
Expand Down

0 comments on commit 0554278

Please sign in to comment.