hashema¶ ↑
Hashema
lets you validate JSONable objects (hashes and arrays) against a schema, and assert their validity in your RSpec examples.
Installation¶ ↑
gem install hashema
Or, if you're using bundler, put this in your Gemfile
:
gem 'hashema'
Hashema
hooks into your RSpec config to provide the conform_to_schema
matcher. If rspec
is listed in your Gemfile
, you should be able to use conform_to_schema
in your tests with no further setup.
RSpec usage¶ ↑
With hashema and RSpec, it's easy to ensure your JSON APIs return the data your clients expect.
describe BlogSerializer do before { @serializer = BlogSerializer.new(Blog.create) } describe '#as_json' do subject { @serializer.as_json } SCHEMA = { url: /^https?:\/\/.+/, posts: [ { title: String, published: [true, false] } ] } it { is_expected_to conform_to_schema SCHEMA } end end
The Schema DSL by example¶ ↑
Allowing any value¶ ↑
expect( Rotation.new('squirrel') ).to conform_to_schema Object
Checking for an exact match¶ ↑
expect( {error: 'not found'} ).to conform_to_schema({error: 'not found'})
Checking for membership in a class¶ ↑
expect( {berzerker: 'pasta'} ).to conform_to_schema Hash
Checking that a string value matches a regular expression¶ ↑
expect( 'Hello! My name is Fridge.' ).to conform_to_schema /^Hello! My name is \w+\.$/
Checking for inclusion in a set of alternatives¶ ↑
expect( {is_awesome: true} ).to conform_to_schema({is_awesome: [true, false]})
Checking for inclusion in a range of legal values¶ ↑
expect( {kyu_rank: 17} ).to conform_to_schema({kyu_rank: 1..30})
Checking that all elements of an array share a schema¶ ↑
expect( [{name: 'Melody'}, {name: 'Elias'}, {name: 'Yoda'}] ).to conform_to_schema [{name: String}]
Matching an array of items that may have different schemas¶ ↑
expect( [{cash: '12.33'}, {credit: '28.95'}, {cash: '40.70'}] ).to conform_to_schema( [ [{cash: /^\d+\.\d\d$/}, {credit: /^\d+\.\d\d$/}] ] )
Making a hash key optional¶ ↑
expect({entree: "eggs"}) .to conform_to_schema({entree: String, side: Hashema::Optional(String)})
RSpec matcher options¶ ↑
with_indifferent_access¶ ↑
Rails fans will be familiar with ActiveSupport's HashWithIndifferentAccess
, which treats symbol and string keys as interchangeable. Calling with_indifferent_access
on the conform_to_schema
matcher will make the matcher similarly tolerant, allowing you to match a hash with string keys against a schema with symbol keys. This is especially useful when writing schemas for JSON, since parsed objects will always have string keys.
get :show schema = { url: /^https?:\/\/.+/, posts: [ { title: String, published: [true, false] } ] } expect(JSON(response.body)).to conform_to_schema(schema).with_indifferent_access
Hashema
without RSpec¶ ↑
There are times when you want to validate the structure of a data object in your production code. For example, if your program parses data from a user-created file, you might want to check that the data you read in match the schema you expect. For such situations, you can use Hashema::Validator
.
The API of Hashema::Validator
consists of an initializer and two instance methods: valid?
and failure_message
. The initializer takes an object to validate and a schema, in that order. valid?
will return true
iff the object conforms to the schema.
validator = Hashema::Validator.new( # the object to validate { blog: { url: 'http://www.blagoblag.com', posts: [ { title: 'hello', published: true }, { title: 'test', published: false } ] } }, # the schema { blog: { url: /^https?:\/\//, posts: [ { title: String, published: [true, false] } ] } } ) validator.valid? # true
If valid?
is false
, failure_message
will return a human-readable description of the failure, which includes, at a minimum:
-
the path through the data structure to the point where the first mismatch occurred
-
the expected value at that point
-
the actual value
validator = Hashema::Validator.new( # the object to validate { blog: { url: 'http://www.blagoblag.com', posts: [ { title: 'hello', published: true }, { title: 123, published: false } ] } }, # the schema { blog: { url: /^https?:\/\//, posts: [ { title: String, published: [true, false] } ] } } ) validator.valid? # false puts validator.failure_message # prints: # expected /blog/posts/1/title to match # String # but got # 123