No release in over 3 years
Low commit activity in last 3 years
RSpec matchers for JSON strings with the power of built-in matchers and composable matchers
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
 Dependencies

Development

~> 1.11

Runtime

~> 2.0
~> 3.0
 Project Readme

Gem Version Test Coverage Status Code Climate

RSpec::ComposableJSONMatchers

RSpec::ComposableJSONMatchers provides be_json matcher, which lets you express expected structures on JSON strings with the power of RSpec's built-in matchers and composable matchers.

json = '{ "foo": 1, "bar": 2 }'
expect(json).to be_json a_kind_of(Hash)
expect(json).to be_json matching(foo: 1, bar: a_kind_of(Integer))
expect(json).to be_json including(foo: 1)

Installation

Adding rspec-composable_json_matchers dependency

Add this line to your Gemfile:

gem 'rspec-composable_json_matchers'

And then run:

$ bundle install

Enabling be_json matcher

To make the be_json matcher available in every example, add the following line to your spec/spec_helper.rb:

# spec/spec_helper.rb
require 'rspec/composable_json_matchers/setup'

Or if you prefer more explicit way, add the following snippet:

# spec/spec_helper.rb
require 'rspec/composable_json_matchers'

RSpec.configure do |config|
  config.include RSpec::ComposableJSONMatchers
end

If you want to enable the be_json matcher only specific examples rather than every example, include the RSpec::ComposableJSONMatchers in the example groups:

# spec/something_spec.rb
require 'rspec/composable_json_matchers'

describe 'something' do
  include RSpec::ComposableJSONMatchers
end

Usage

The be_json matcher takes another matcher or a JSON value (hash, array, numeric, string, true, false, or nil).

When a matcher is given, it passes if actual string can be decoded as JSON and the decoded value passes the given matcher:

expect('{ "foo": 1, "bar": 2 }').to be_json a_kind_of(Hash)
expect('{ "foo": 1, "bar": 2 }').to be_json matching(foo: 1, bar: 2)
expect('{ "foo": 1, "bar": 2 }').to be_json including(foo: 1)

expect('["foo", "bar"]').to be_json a_kind_of(Array)
expect('["foo", "bar"]').to be_json matching(['foo', 'bar'])
expect('["foo", "bar"]').to be_json including('foo')

expect('null').to be_json(nil)

When a JSON value is given, it's handled as be_json matching(value) (matching is an alias of the match matcher):

# Equivalents
expect('{ "foo": 1, "bar": 2 }').to be_json(foo: 1, bar: 2)
expect('{ "foo": 1, "bar": 2 }').to be_json matching(foo: 1, bar: 2)

You can compose matchers via given matchers:

expect('{ "foo": 1, "bar": 2 }').to be_json matching(
  foo: a_kind_of(Integer),
  bar: a_kind_of(Integer)
)

expect('{ "foo": 1, "bar": 2 }').to be_json including(foo: a_kind_of(Integer))

For more practical example, see spec/example_spec.rb for the GitHub Meta API.

Combinations with built-in matchers

Since decoded JSON is a hash or an array in most cases, you may want to use any of the following built-in matchers.

Note that you can always use the match matcher (internally uses #===) instead of the eq matcher (internally uses #==), because there's no object that is parsed from JSON and behaves differently between #== and #===.

Of course, any other custom matchers can also be used.

Also, using the built-in matcher aliases is recommended since it reads well:

# Equivalents
expect('{ "a": "foo" }').to be_json matching(a: a_string_including('oo')) # Matcher aliases
expect('{ "a": "foo" }').to be_json match(a: include('oo')) # Original matcher names

matching

  • Alias of match matcher
  • Supported structure: Hash and Array
  • Accepts matchers as arguments: Yes
expect('{ "foo": 1, "bar": 2 }').to be_json matching(foo: 1, bar: a_kind_of(Integer))
expect('["foo", "bar"]').to be_json matching(['foo', a_string_starting_with('b')])

including

  • Alias of include matcher
  • Supported structure: Hash and Array
  • Accepts matchers as arguments: Yes
expect('{ "foo": 1, "bar": 2 }').to be_json including(foo: 1)
expect('["foo", "bar"]').to be_json including('foo')

all

  • all matcher
  • Supported structure: Array
  • Accepts matchers as arguments: Yes
expect('["foo", "bar"]').to be_json all be_a(String)

containing_exactly

  • Alias of contain_exactly matcher
  • Supported structure: Array
  • Accepts matchers as arguments: Yes
expect('["foo", "bar"]').to be_json containing_exactly('bar', 'foo')

starting_with

  • Alias of start_with matcher
  • Supported structure: Array
  • Accepts matchers as arguments: Yes
expect('["foo", "bar"]').to be_json starting_with('foo')

ending_with

  • Alias of end_with matcher
  • Supported structure: Array
  • Accepts matchers as arguments: Yes
expect('["foo", "bar"]').to be_json ending_with('bar')

having_attributes

  • Alias of have_attributes matcher
  • Supported structure: Hash and Array
  • Accepts matchers as arguments: Yes
expect('{ "foo": 1, "bar": 2 }').to be_json having_attributes(keys: [:foo, :bar])
expect('["foo", "bar"]').to be_json having_attributes(size: 2)

a_kind_of

  • Alias of be_a_kind_of matcher
  • Supported structure: Hash and Array
  • Accepts matchers as arguments: No
expect('{}').to be_json a_kind_of(Hash)
expect('[]').to be_json a_kind_of(Array)

Configuration

The be_json matcher internally uses JSON.parse to decode JSON strings. The default parser options used in the be_json matcher is { symbolize_names: true }, so you need to pass a hash with symbol keys as an expected structure.

If you prefer string keys, add the following snippet to your spec/spec_helper.rb:

# spec/spec_helper.rb
RSpec::ComposableJSONMatchers.configure do |config|
  config.parser_options = { symbolize_names: false }
end

License

Copyright (c) 2016 Yuji Nakayama

See the LICENSE.txt for details.