A long-lived project that still receives updates
A drop-in replacement for RSpec's built-in JSON formatter that adds structured test failure data, making it easy to programmatically analyze test results.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 13.0
~> 3.0
~> 1.0

Runtime

 Project Readme

RSpec::EnrichedJson

CI Gem Version Ruby Style Guide

A drop-in replacement for RSpec's built-in JSON formatter that enriches the output with structured failure data. This makes it easy to programmatically analyze test results, extract expected/actual values, and build better CI/CD integrations.

Quick Demo

To see the difference between RSpec's built-in JSON formatter and this enriched formatter:

ruby demo.rb

This interactive demo script runs the same failing tests with both formatters and shows you the difference side-by-side.

What you'll see:

  • Built-in formatter: Failure information embedded in string messages
  • Enriched formatter: Adds structured data with:
    • expected: The expected value as a proper JSON object
    • actual: The actual value as a proper JSON object
    • matcher_name: The RSpec matcher class used
    • original_message: Preserved when custom messages are provided

Requirements

  • Ruby 2.7 or higher
  • RSpec 3.0 or higher

Installation

Add this line to your application's Gemfile:

gem "rspec-enriched_json"

And then execute:

$ bundle install

Or install it yourself as:

$ gem install rspec-enriched_json

Usage

Use it just like RSpec's built-in JSON formatter:

# Command line
rspec --format RSpec::EnrichedJson::Formatters::EnrichedJsonFormatter

# Or in your .rspec file
--format RSpec::EnrichedJson::Formatters::EnrichedJsonFormatter

# Or in spec_helper.rb
RSpec.configure do |config|
  config.formatter = RSpec::EnrichedJson::Formatters::EnrichedJsonFormatter
end

What's Different?

Standard RSpec JSON Output

With RSpec's built-in JSON formatter, failure information comes as a string:

{
  "exception": {
    "message": "\nexpected: \"Hello, Ruby!\"\n     got: \"Hello, World!\"\n\n(compared using ==)\n"
  }
}

Enriched JSON Output

With this gem, you get structured data alongside the original message:

{
  "exception": {
    "class": "RSpec::EnrichedJson::EnrichedExpectationNotMetError",
    "message": "\nexpected: \"Hello, Ruby!\"\n     got: \"Hello, World!\"\n\n(compared using ==)\n",
    "backtrace": ["./spec/example_spec.rb:5:in `block (2 levels) in <top (required)>'"]
  },
  "details": {
    "expected": "Hello, Ruby!",
    "actual": "Hello, World!",
    "matcher_name": "RSpec::Matchers::BuiltIn::Eq",
    "original_message": null,
    "diffable": true
  }
}

Features

  • Drop-in replacement: Inherits from RSpec's JsonFormatter, maintaining 100% compatibility.
  • Structured data extraction: Expected and actual values as proper JSON objects.
  • Rich object support: Arrays, hashes, and custom objects are properly serialized.
  • Original message preservation: When you override with a custom message, the original is preserved.
  • Graceful degradation: Regular exceptions (non-expectation failures) work normally.
  • Enhanced metadata capture: Test location, tags, hierarchy, and custom metadata.
  • Robust error recovery: Handles objects that fail to serialize without crashing.
  • Diff information: Includes diffable to help tools determine if values can be meaningfully diffed.

Examples

Simple Values

expect(1 + 1).to eq(3)
# details: { "expected": 3, "actual": 2 }

Collections

expect([1, 2, 3]).to eq([1, 2, 4])
# details: { "expected": [1, 2, 4], "actual": [1, 2, 3] }

Complex Objects

Product = Struct.new(:name, :price)
expect(Product.new("Laptop", 999)).to eq(Product.new("Laptop", 899))
# details includes class info and struct values

Custom Messages

expect(balance).to be >= required,
  "Insufficient funds: $#{balance} available, $#{required} required"
# exception.message: "Insufficient funds: $50 available, $100 required"
# details: { "original_message": "expected: >= 100\n     got:    50" }

Metadata Capture

it "validates user input", :slow, :db, priority: :high do
  expect(user).to be_valid
end
# metadata includes:
# - location: "./spec/models/user_spec.rb:42"
# - absolute_file_path: "/path/to/project/spec/models/user_spec.rb"
# - tags: { "slow": true, "db": true, "priority": "high" }
# - example_group_hierarchy: ["User", "validations", "email format"]

Use Cases

  • CI/CD Integration: Parse test results to create rich error reports.
  • Test Analytics: Track which values commonly cause test failures.
  • Debugging Tools: Build tools that can display expected vs actual diffs.
  • Learning Platforms: Provide detailed feedback on why tests failed.

How It Works

The gem works by:

  1. Patching RSpec's expectation system to capture structured data when expectations fail.
  2. Extending the JsonFormatter to include this data in the JSON output.
  3. Maintaining full backward compatibility with existing tools.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests.

This project uses StandardRB for code formatting and style. Before committing:

# Check for style violations
bundle exec standardrb

# Auto-fix style violations
bundle exec standardrb --fix

Additional Features

Passing Test Value Capture

The formatter also captures expected/actual values for passing tests, useful for:

  • Test analytics and insights.
  • Understanding test coverage patterns.
  • Debugging flaky tests.

Negation Detection

Tests using not_to or to_not include a negated: true flag in the details.

Serialization

Values are serialized using Oj in object mode, providing:

  • Circular reference handling.
  • Proper Ruby object serialization.
  • Excellent performance.
  • Special handling for Regexp objects (serialized as inspect strings).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/firstdraft/rspec-enriched_json.

License

The gem is available as open source under the terms of the MIT License.