Skip to content
This repository has been archived by the owner on Oct 7, 2021. It is now read-only.

DogParkLabs/json_rspec_match_maker

Repository files navigation

JsonRspecMatchMaker

Helper class for writing custom RSpec matchers for JSON api endpoints.

DRY up API expectations, without losing the specificity sacrificed by a schema-based approach.

Installation with Rails

Add this line to your application's Gemfile:

gem 'json_rspec_match_maker', require: false

And then execute:

$ bundle

Update your rails_helper.rb with:

# require the gem
require 'json_rspec_match_maker'

# require your custom matchers you'll be writing
Dir[Rails.root.join('spec/support/matchers/json_matchers/**/*.rb')].each do |f|
  require f
end

Usage

Matchers are instantiated with some object and a match definition.

If our address class looks like:

class Address
  attr_reader :street1, :street2, :city, :state, :zip
end

And we serialize that into JSON like:

{
  address: {
    street1: '18 Streety Street',
    street2: 'APT 22B',
    city: 'Citytown',
    state: 'NY',
    zip: '11111'
  }
}

Then we can define a matcher like:

module JsonMatchers
  def be_valid_json_for_address(address)
    JsonRspecMatchMaker::Base.new(address, %w[street1 street2 city state zip], prefix: 'address')
  end
end

And use it in a spec like:

describe 'my api', type: :request do
  let(:test_address) { Address.new('Street', '2', 'Place', 'ZZ', '00000') }
  it 'gets some stuff' do
    get '/api/address'
    expect(JSON.parse(response.body)).to be_valid_json_for_address(test_address)
  end
end

You can also pass a custom Proc to override the methods called to fetch the value for a key: (these need to be last in the array - taking advantage of ruby sytanx sugar omitting surrounding brackets)

match = [
  'id',
  'name' => ->(object) { object.full_name }
]

Single nested objects are simple:

match = [
  'user.address.street1'
]

Nested lists have one extra step :each should be a proc that fetches the list to iterate through :attributes should be another definition, following same rules as the outer one

match = [
  'id',
  'name',
  'photographs_attributes' => {
    each: -> (object) { object.photographs.visible },
    attributes: %w[id caption url]
  }
]

You may want to break out more complicated definitions into their own class:

module JsonMatchers
  class AddressMatcher < JsonRspecMatchMaker::Base
    MATCH = %w[street1 street2 city state zip].freeze
    
    def initialize(address)
      super(address, MATCH, prefix: 'address')
    end
  end

  def be_valid_json_for_address(address)
    AddressMatcher.new(address)
  end
end

You might want your matchers to be more dynamic so you could do something like:

class AddressMatcher < JsonRspecMatchMaker::Base
  def initialize(address, state_format)
    match_definition = set_match_def(state_format)
    super(address, match_definition)
  end
  
  def set_match_def(state_format)
    [
      'state' => ->(instance) { instance.state.formatted(state_format) }
    ]
  end
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/json_rspec_match_maker. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

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

Code of Conduct

Everyone interacting in the JsonRspecMatchMaker project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.