Skip to content

[#668] Added 'JsonTrait' for JSON response path and schema assertions.#679

Merged
AlexSkrypnyk merged 3 commits into
mainfrom
feature/668-json-trait
Jul 6, 2026
Merged

[#668] Added 'JsonTrait' for JSON response path and schema assertions.#679
AlexSkrypnyk merged 3 commits into
mainfrom
feature/668-json-trait

Conversation

@AlexSkrypnyk

@AlexSkrypnyk AlexSkrypnyk commented Jul 6, 2026

Copy link
Copy Markdown
Member

Closes #668

Summary

Adds JsonTrait, giving JSON API responses the same assertion treatment XmlTrait gives XML bodies: valid-JSON checks, JSONPath value/type/existence/count assertions, and JSON Schema validation.
Content can come from the live page, a fixture file, or an inline PyString, with fixture/PyString content always taking precedence, mirroring the resolution order already used by XmlTrait.
JSON Schema validation is wired as an optional dependency so consumers who do not need it are not forced to install it.

Changes

  • Added src/JsonTrait.php with 19 step definitions: two content-fixture Given steps (from a file, from a PyString), format-validity Then steps (should be / should not be in JSON format), JSONPath assertions (should exist / not exist / be equal to / not be equal to / contain / not contain / match / not match / be null / be true / be false / have :count element(s)), two JSON Schema Then steps (inline PyString and from a file), and a When I print last JSON response debugging step.
  • JSON decoding is cached per response body (hashed with md5()) so repeated JSONPath queries against the same page do not re-decode on every assertion, and the cache is cleared in BeforeScenario/AfterScenario hooks and whenever fixture content is set.
  • Added softcreatr/jsonpath to require in composer.json for JSONPath evaluation, and justinrainbow/json-schema to require-dev plus a suggest entry, since only the two schema-validation steps need it (jsonValidateSchema() guards with class_exists(Validator::class)).
  • Registered JsonTrait in tests/behat/bootstrap/FeatureContext.php.
  • Added tests/behat/features/json.feature covering every step in both the passing and failing direction, plus a scenario asserting that decoded JSON is correctly reloaded when navigating between different JSON responses.
  • Added fixtures tests/behat/fixtures/json_valid.json, json_invalid.json, json_alt.json, and json_schema.json.
  • Regenerated STEPS.md and added the JsonTrait row to the trait table in README.md.

Screenshots

N/A

Before / After

Before: only XmlTrait exists - JSON API responses have no shared
assertion surface, so consumers hand-roll json_decode() and array
digging in project-level step definitions.

┌───────────────────────────────────────────┐
│ XmlTrait                                  │
├───────────────────────────────────────────┤
│ response content from file                │
│ response content (PyString)               │
│ response in / not in XML format           │
│ XML element exist / equal / contain / ... │
│ response matches XSD schema               │
└───────────────────────────────────────────┘


After: JsonTrait mirrors XmlTrait's shape - fixture-or-PyString
content, a format-validity assertion, path-based assertions, and
schema validation - so JSON responses get the same treatment.

┌───────────────────────────────────────────┐     ┌─────────────────────────────────────────┐
│ XmlTrait                                  │     │ JsonTrait                               │
├───────────────────────────────────────────┤     ├─────────────────────────────────────────┤
│ response content from file                │     │ response JSON from file                 │
│ response content (PyString)               │     │ response JSON content (PyString)        │
│ response in / not in XML format           │     │ response in / not in JSON format        │
│ XML element exist / equal / contain / ... │     │ JSON path exist / equal / contain / ... │
│ response matches XSD schema               │     │ response matches JSON schema            │
└───────────────────────────────────────────┘     └─────────────────────────────────────────┘

Summary

  • Added JsonTrait for JSON response assertions with valid-JSON checks, JSONPath-based value/type/existence/count assertions, JSON Schema validation, and a debug step to print the last JSON response.
  • Registered the trait in FeatureContext and added Behat coverage plus JSON/schema fixtures for positive and negative scenarios.
  • Updated generated docs in STEPS.md and README.md, and added the required JSONPath / JSON Schema Composer dependencies.

Notes

  • Verified step/trait conventions from CONTRIBUTING.md for the new Behat step definitions: assertion methods are prefixed with the trait name and Assert, Given/When/Then strings use the expected wording (I <verb> for When, should/should not for Then), and no step definitions rely on regex patterns—no Critical violations found.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds a new JsonTrait for JSON response assertions, including JSON validity checks, JSONPath-based value/path assertions, JSON Schema validation, Behat wiring, fixtures, and documentation updates.

Changes

JsonTrait feature

Layer / File(s) Summary
Dependency declarations
composer.json
Adds softcreatr/jsonpath, justinrainbow/json-schema, and a schema-validation suggestion.
JsonTrait core structure, lifecycle, and content setup
src/JsonTrait.php
Defines the trait, scenario reset hooks, content-setting steps, and JSON content resolution helpers.
JSON validity and print steps
src/JsonTrait.php
Adds valid/invalid JSON checks and a step to print the last JSON response.
JSONPath assertions and helpers
src/JsonTrait.php
Adds JSONPath existence, equality, containment, regex, null/boolean, and count assertions plus supporting helpers.
JSON Schema validation
src/JsonTrait.php
Adds inline and file-based schema validation steps and runtime validation logic.
Feature wiring, fixtures, tests, and docs
tests/behat/bootstrap/FeatureContext.php, tests/behat/fixtures/*, tests/behat/features/json.feature, README.md, STEPS.md
Registers JsonTrait, adds JSON fixtures and Behat scenarios, and updates the docs indexes and trait documentation.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Suggested labels: Needs review

Poem

A nibble, a hop, a JSON breeze,
I test each path with quiet ease.
🐇 Valid, invalid, schema too—
The burrow’s steps now peek right through.
Counts and booleans, neat and bright,
My rabbit heart says: JSON feels right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly names the new JsonTrait and its JSON path/schema assertion focus.
Linked Issues check ✅ Passed The changes add JsonTrait, JSONPath/schema assertions, tests, and docs matching #668's requirements.
Out of Scope Changes check ✅ Passed The added composer deps, fixtures, tests, and docs all support the JsonTrait feature set.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/668-json-trait

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PHPStan (2.2.2)

Composer install failed: the lock file is not up to date with the latest changes in composer.json. Run composer update and commit the updated composer.lock.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@composer.json`:
- Around line 24-25: The dependency constraint for softcreatr/jsonpath is
incompatible with the project’s declared PHP baseline in composer.json. Update
the package requirement in composer.json by either raising the PHP constraint to
match the minimum version needed by the allowed jsonpath releases or narrowing
the jsonpath version range to a release that still supports PHP 8.2, keeping the
package and PHP requirements consistent.

In `@src/JsonTrait.php`:
- Around line 109-136: The JSON decode-and-check logic is duplicated in
JsonTrait across jsonAssertResponseIsJson, jsonAssertResponseIsNotJson,
jsonPrintLastResponse, and jsonValidateSchema. Extract this into a shared helper
such as jsonDecodeOrFail (or reuse jsonDecode) and update those methods to call
it, keeping the failure message format consistent through the existing
jsonResolveContent and jsonDecode-related flow.
- Around line 337-351: The jsonAssertPathCount() step currently coerces any
non-numeric :count value to 0 via (int) $count, which can hide malformed test
input. Update this method in JsonTrait to validate that $count is numeric before
casting, and throw an ExpectationException with a clear message when it is not;
keep the existing count comparison logic for valid values and preserve the
jsonResolveSingle() / getSession()->getDriver() flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f8dc8205-b595-4131-9dfc-dab14b0650da

📥 Commits

Reviewing files that changed from the base of the PR and between 5802aec and 2eb16c8.

📒 Files selected for processing (10)
  • README.md
  • STEPS.md
  • composer.json
  • src/JsonTrait.php
  • tests/behat/bootstrap/FeatureContext.php
  • tests/behat/features/json.feature
  • tests/behat/fixtures/json_alt.json
  • tests/behat/fixtures/json_invalid.json
  • tests/behat/fixtures/json_schema.json
  • tests/behat/fixtures/json_valid.json

Comment thread composer.json
Comment thread src/JsonTrait.php
Comment thread src/JsonTrait.php
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.95%. Comparing base (5802aec) to head (322bb7d).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #679      +/-   ##
==========================================
+ Coverage   96.85%   96.95%   +0.10%     
==========================================
  Files          46       47       +1     
  Lines        3818     3946     +128     
==========================================
+ Hits         3698     3826     +128     
  Misses        120      120              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/JsonTrait.php (1)

598-622: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extending jsonDecodeLoose() to also cover the schema decode.

The response-content decode now uses jsonDecodeLoose() (line 605), but the schema decode block (lines 607-610) still duplicates the same decode/json_last_error()/throw pattern inline. Parameterizing the error-message prefix would complete the consolidation this commit already started for the other three call sites.

♻️ Proposed refactor
-  protected function jsonDecodeLoose(string $content): mixed {
+  protected function jsonDecodeLoose(string $content, string $error_prefix = 'The response is not valid JSON'): mixed {
     $data = json_decode($content);

     if (json_last_error() !== JSON_ERROR_NONE) {
-      throw new ExpectationException(sprintf('The response is not valid JSON: %s', json_last_error_msg()), $this->getSession()->getDriver());
+      throw new ExpectationException(sprintf('%s: %s', $error_prefix, json_last_error_msg()), $this->getSession()->getDriver());
     }

     return $data;
   }
     $data = $this->jsonDecodeLoose($this->jsonResolveContent());

-    $schema = json_decode($schema_json);
-    if (json_last_error() !== JSON_ERROR_NONE) {
-      throw new ExpectationException(sprintf('The provided JSON schema is not valid JSON: %s', json_last_error_msg()), $this->getSession()->getDriver());
-    }
+    $schema = $this->jsonDecodeLoose($schema_json, 'The provided JSON schema is not valid JSON');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/JsonTrait.php` around lines 598 - 622, The schema decode in
jsonValidateSchema still duplicates the loose JSON decode error handling that
was consolidated elsewhere. Refactor the schema parsing block to reuse
jsonDecodeLoose() for decoding the $schema_json input, and parameterize the
error-message prefix so the thrown ExpectationException still clearly identifies
schema parsing failures. Keep the existing Validator flow and unique symbols
jsonValidateSchema, jsonDecodeLoose, and ExpectationException as the integration
points.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/JsonTrait.php`:
- Around line 598-622: The schema decode in jsonValidateSchema still duplicates
the loose JSON decode error handling that was consolidated elsewhere. Refactor
the schema parsing block to reuse jsonDecodeLoose() for decoding the
$schema_json input, and parameterize the error-message prefix so the thrown
ExpectationException still clearly identifies schema parsing failures. Keep the
existing Validator flow and unique symbols jsonValidateSchema, jsonDecodeLoose,
and ExpectationException as the integration points.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c5d0acee-068e-419b-8bca-651dcd4cfcee

📥 Commits

Reviewing files that changed from the base of the PR and between 2eb16c8 and 322bb7d.

📒 Files selected for processing (2)
  • src/JsonTrait.php
  • tests/behat/features/json.feature

@AlexSkrypnyk AlexSkrypnyk added the Needs review Pull request needs a review from assigned developers label Jul 6, 2026
@AlexSkrypnyk AlexSkrypnyk merged commit fbaa2b2 into main Jul 6, 2026
14 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/668-json-trait branch July 6, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs review Pull request needs a review from assigned developers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a JSON response assertion trait (path and schema)

1 participant