Why you should use dry-validation over json-schema?
You working with JSON responses coming from APIs and you want to validate their schemas? What’s your first choice? Probably you go with the great way that Thoughtbot guys explained in this article. I’ve launched and tested various projects based on that technique but I always felt I’m doing something wrong because the process was so painful. Let me explain; If you want to validate a JSON response first you should create a JSON schema based on it then you’d write your test and get some errors because of a comma/double quotation being missed.
An example of the old way of testing JSON responses. First, define the JSON schema:
{
"type": "object",
"required": ["user"],
"properties": {
"user" : {
"type" : "object",
"required" : [
"id",
"token",
"token_expired_at",
"email",
"firstname",
"lastname",
"gender",
"phone_number",
"birthday"
],
"properties" : {
"id" : { "type" : "integer" },
"token" : { "type" : "string" },
"token_expired_at" : { "type" : "string" },
"email" : { "type" : "string" },
"lastname" : { "type" : "string" },
"firstname" : { "type" : "string" },
"gender" : { "type" : "string" },
"phone_number" : { "type" : "string" },
"brithday" : { "type" : "string", "format": "date-time" },
"updated_at" : { "type" : "string", "format": "date-time" },
"created_at" : { "type" : "string", "format": "date-time" },
}
}
}
}
Then write your test
describe "Sign in" do
context "anonymous user" do
it "returns data in the specified format" do
sign_in_as_anonymous
expect(response).to match_response_schema("users/anonymous_detail")
end
end
end
Not speaking of bad errors, you have to switch to JSON schema syntax and Ruby over and over again (context switching) and there you are you’ve lost your time and focus. Now I’m going to show you a better way to validate your mighty JSON with ease.
Creating the custom matcher
# Gemfile
group :test do
gem "dry-validation"
end
# spec/support/matchers/match_schema.rb
RSpec::Matchers.define :match_schema do |schema|
match do |response|
@result = schema.call(JSON.parse(response.body, symbolize_names: true))
@result.success?
end
def failure_message
@result.errors
end
end
Note: Here I’ve chosen
/spec/support/matchers/*
for my custom matchers with a name convention ofmatch_*
. Also inschema.call
schema
is a dry-validation class andcall
initializes the schema class with the given parameters then I return the result of validation which is a true/false value. I’ve even definedfailure_message
to be able to see the errors that have been generated from validating the schema inside Rspec result.
Defining the anonymous user schema
Add this to your spec/rails_helper.rb
require "support/views/schemas"
Now defining anonymous user schema:
AnonymousUserSchema = Dry::Validation.Schema do
required(:user).schema do
required(:email)
required(:token).filled
required(:token_expired_at).filled
required(:firstname)
required(:lastname)
required(:gender)
required(:phone_number)
required(:birthday)
end
end
Now you can change your spec to this:
describe "Sign in" do
context "anonymous user" do
it "returns data in the specified format" do
sign_in_as_anonymous
expect(response).to match_schema(AnonymousUserSchema)
end
end
end
There you go! What if when I added a new field? You just add the field with the needed condition to end of the block.
AnonymousUserSchema = Dry::Validation.Schema do
required(:user).schema do
required(:email)
required(:token).filled
required(:token_expired_at).filled
required(:firstname)
required(:lastname)
required(:gender)
required(:phone_number)
required(:birthday)
required(:age).maybe(int?)
end
end
With dry-validation
you can do pretty much everything. For further information please check out the link down below.
http://dry-rb.org/gems/dry-validation/
I hope you have enjoyed :D
Top comments (4)
I love this! What file do you put the schema in?
I'm also interested in whether this could also be used within the API consumer itself, to render error messages or to detect an outage from malformed JSON.
We used this more on testing side but now days I'd recommend keep it simple and using just a minittest helper or a rspec helper instead of brining a whole library unless necessary and if I'm not wrong we used to write them in
spce/schemas
.is there any benefits compared to using a tool that cover the whole spec ? ex github.com/ruby-json-schema/json-s...
Not really if you're comfortable maintaining your JSON schemas through JSON files instead of Ruby DSL.