Skip to content

Commit

Permalink
Add Equality assertions for json_path
Browse files Browse the repository at this point in the history
Users can now assert on json path result. For example:
`json_path("$.shop.total", is(json!(4)))`.

Also update the log settings to handle both unit and compound
assertions for all predicates. Json path introduced some
complexity in the log messages that should be better handle later
through a trait.

Fix #25
  • Loading branch information
theredfish committed Mar 28, 2023
1 parent 9f683a8 commit fa7480f
Show file tree
Hide file tree
Showing 11 changed files with 36 additions and 43 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ url = "2.3.1"
futures = "0.3.25"
strum = { version = "0.24.1", features = ["derive"] }
strum_macros = "0.24.3"
jsonpath-rust = "0.2.1"
jsonpath-rust = "0.2.6"

[dev-dependencies]
tokio = { version = "1.24.2", features = ["macros"] }
reqwest = { version = "0.11.14", features = ["json"] }
httpmock = "0.6.7"
async-trait = "0.1.63"
test-case = "2.2.2"
test-case = "3.0.0"
2 changes: 1 addition & 1 deletion src/assert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl Assert {
self
}

/// Asserts the json body of the response.
/// Asserts the value found at the given json path.
pub fn json_path<T>(self, path: &str, expr: Expression<T>) -> Assert
where
T: JsonPathDsl<Value>,
Expand Down
3 changes: 2 additions & 1 deletion src/assertion/impls/json_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ mod tests {
let path = "$.shop";
let value = json_stub().path(path).unwrap();
let jsonpath_result = JsonPathResult { path, value };
// commented element highlight what was removed from initial
// commented element highlights what was removed from initial
// json data.
let expected_json = json!({
"orders": [
Expand Down Expand Up @@ -261,6 +261,7 @@ mod tests {
});

let assertion = jsonpath_result.is_ne(&expected_json);
// Fails because asserted value is equals to the expected value.
assert!(assertion.failed(), "{}", assertion.log());
}
}
Expand Down
39 changes: 20 additions & 19 deletions src/assertion/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,12 @@ pub enum AssertionResult {
/// Represents an assertion log.
///
/// A log is built according to this scheme:
/// - part: \<part\> \[compound_hand_part]
/// - \<predicate\>: \<expected_value\>
/// - was: \<found_value\> (only in case of failure)
///
/// condition: <part> [compound_hand_part] <predicate>
/// expected: <expected_value>
/// was: <found_value>
/// The log will be displayed for both [`LogSettings::StdOut`] and
/// [`LogSettings::StdAssert`]
pub struct AssertionLog(String);

impl AssertionLog {
Expand All @@ -163,28 +165,28 @@ impl AssertionLog {
Hand::Compound(left, right) if part == &Part::StatusCode => {
format!("{left:#?} and {right:#?}")
}
// Hand::Compound(left, _) if part == &Part::JsonPath => format!("'{left}'"),
_ => "Unexpected left hand in right hand".to_string(),
};
let right = match &assertion.right {
Hand::Right(right) => format!("{right:#?}"),
Hand::Compound(left, right) if part == &Part::StatusCode => {
format!("{left:#?} and {right:#?}")
}
// Hand::Compound(_, right) if part == &Part::JsonPath => format!("'{right}'"),
_ => "Unexpected left hand in right hand".to_string(),
};

// The base message is built as a passing case.
let base_message = match part {
Part::Empty => format!("{left} {predicate} {right}"),
_ => format!("{part} {predicate} {right}"),
};

let message = format!("part: {part}");
let message = match &assertion.result {
AssertionResult::Passed => base_message,
AssertionResult::Failed => format!("{base_message}. Found {left}"),
AssertionResult::NotYetStarted => format!("Not yet started : {base_message}"),
AssertionResult::Passed => format!(
"{message}
{predicate}: {right:#?}"
),
AssertionResult::Failed => format!(
"{message}
{predicate}: {right:#?}
was: {left:#?}"
),
AssertionResult::NotYetStarted => format!("Not yet started : {message}"),
AssertionResult::Unprocessable(reason) => format!("{reason}"),
};

Expand Down Expand Up @@ -213,12 +215,11 @@ impl AssertionLog {

let jsonpath_value = right_hand.1;

let message = format!("\ncondition: {part} '{jsonpath}'");

let message = format!("part: {part} '{jsonpath}'");
let message = match &assertion.result {
AssertionResult::Passed => format!(
"{message}
expected: {left_hand:?}"
{predicate}: {left_hand:#?}"
),
AssertionResult::Failed => format!(
"{message}
Expand Down Expand Up @@ -255,8 +256,8 @@ where
pub fn assert(self, log_settings: &LogSettings) -> Assertion<T> {
let message = self.log();
match log_settings {
LogSettings::StdOut => println!("{message}"),
LogSettings::StdAssert => assert!(self.passed(), "{}", message),
LogSettings::StdOut => println!("\n{message}"),
LogSettings::StdAssert => assert!(self.passed(), "\n\n{message}"),
LogSettings::Json => {
let json = serde_json::to_string(&json!(self))
.expect("Unexpected json failure: failed to serialize assertion");
Expand Down
2 changes: 1 addition & 1 deletion src/dsl/http/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub trait JsonBodyDsl<T> {
match predicate {
Is => self.is(actual).assert(log_settings),
IsNot => self.is_not(actual).assert(log_settings),
_ => unimplemented!("Invalid predicate for the json body DSL : {predicate}"),
_ => unimplemented!("Invalid predicate for the json body DSL: {predicate}"),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/dsl/http/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub trait HeadersDsl<T> {
Predicate::IsNot => self.is_not(actual).assert(log_settings),
Predicate::Contains => self.contains(actual).assert(log_settings),
Predicate::DoesNotContain => self.does_not_contain(actual).assert(log_settings),
_ => unimplemented!("Invalid predicate for the header DSL : {predicate}"),
_ => unimplemented!("Invalid predicate for the header DSL: {predicate}"),
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/dsl/http/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl StatusCodeDsl<StatusCode> for StatusCode {
match predicate {
Predicate::Is => self.is(actual).assert(log_settings),
Predicate::IsNot => self.is_not(actual).assert(log_settings),
_ => unimplemented!(),
_ => unimplemented!("Invalid predicate for the status code DSL: {predicate}"),
}
}
}
Expand All @@ -82,7 +82,7 @@ impl StatusCodeDsl<StatusCode> for u16 {
match predicate {
Predicate::Is => self.is(actual).assert(log_settings),
Predicate::IsNot => self.is_not(actual).assert(log_settings),
_ => unimplemented!(),
_ => unimplemented!("Invalid predicate for the status code DSL: {predicate}"),
}
}
}
Expand All @@ -96,7 +96,7 @@ impl StatusCodeDsl<StatusCode> for Range<StatusCode> {
) -> Assertion<u16> {
match predicate {
Predicate::Between => self.is_between(actual).assert(log_settings),
_ => unimplemented!(),
_ => unimplemented!("Invalid predicate for the status code DSL: {predicate}"),
}
}
}
Expand All @@ -110,7 +110,7 @@ impl StatusCodeDsl<StatusCode> for Range<u16> {
) -> Assertion<u16> {
match predicate {
Predicate::Between => self.is_between(actual).assert(log_settings),
_ => unimplemented!(),
_ => unimplemented!("Invalid predicate for the status code DSL: {predicate}"),
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/dsl/http/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ pub trait TimeDsl<T> {
/// milliseconds.
fn is_less_than(&self, actual: T) -> Assertion<u64>;
/// Evaluates the time assertion to run based on the [`Predicate`]
fn eval(&self, actual: T, operator: Predicate, log_settings: &LogSettings) -> Assertion<u64> {
match operator {
fn eval(&self, actual: T, predicate: Predicate, log_settings: &LogSettings) -> Assertion<u64> {
match predicate {
Predicate::LessThan => self.is_less_than(actual).assert(log_settings),
_ => unimplemented!(),
_ => unimplemented!("Invalid predicate for the time DSL: {predicate}"),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/dsl/json_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub trait JsonPathDsl<T> {
match predicate {
Is => self.is(jsonpath_res).assert(log_settings),
IsNot => self.is_not(jsonpath_res).assert(log_settings),
_ => unimplemented!("Invalid predicate for the json body DSL : {predicate}"),
_ => unimplemented!("Invalid predicate for the json path DSL: {predicate}"),
}
}
}
Expand Down
3 changes: 0 additions & 3 deletions src/dsl/part.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ pub enum Part {
#[strum(serialize = "response time")]
#[serde(rename = "response time")]
ResponseTime,
/// Represents an empty information about the part.
/// This variant isn't used when producing assertion events.
Empty,
}

#[cfg(test)]
Expand Down
8 changes: 1 addition & 7 deletions tests/assert/json_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,11 @@ async fn json_path_should_be_equal_to_json() -> Result<()> {
let mock_server = HttpMockServer::new();
let mock = mock_server.get_valid_user();

let path = "$";
let expected_json = json!({
"id": 1,
"name": "Isaac",
});

Grillon::new(&mock_server.server.url("/"))?
.get("users/1")
.assert()
.await
.json_path(path, is(expected_json));
.json_path("$.id", is(json!(1)));

mock.assert();

Expand Down

0 comments on commit fa7480f

Please sign in to comment.