0.0
No release in over a year
API testing against declarative schemas
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 0
~> 10.0
~> 3.0

Runtime

 Project Readme

SchemaTest

This gem provides a convenient and flexible way for specifying and testing schema definitions for JSON API response. The mean features are:

  • A simple Ruby-based DSL for capturing JSON objects, including property names, types, and metadata like descriptions
  • Simple ways to version those schemas, and specify new versions efficiently
  • Mechanisms to evaluate a given JSON blob against a schema to verify it matches
  • Mechanisms to write tests for multiple API versions that strike a good balance between efficiency of specifiation vs avoiding unintentional impacts to endpoints. I'll explain that later.

How & why

This gem provides a way to define JSON Schema objects using a simple Ruby DSL, in a way that allows other schemas to be easily composed of existing ones:

# Define a simple user schema
SchemaTest.define :user do
  id
  string :name
  url :avatar_url
end

# Define a new comment schema
SchemaTest.define :comment do
  string :body

  # re-use the user schema, but give the object a different name
  object :user, as: :author
end

This lets you minimise duplication between your schema definitions in a convenient way. And we can then use the generated JSON-Schema data (see below) to validate our app's generated responses against this.

But JSON-Schema already lets you compose schemas, right? What's the point of this?

API versioning

Let's imagine we have some test for our comments API output, something like (pseudocode):

get :comment

assert_schema_matches response.body, schema_for(:comment)

Then, some time later, we create a new version of our API, and some of the serialised objects have changes - new fields, removed fields, fields with different meanings, and so on.

We might change our schema definition:

SchemaTest.define :user do
  id
  string :first_name
  string :last_name
  url :avatar_url
end

... and then we change our user serialiser logic, and write a test for the new API version, and everything passes. Great, right?

No. Not great. Not great because while yes, the tests pass, we've actually changed the output of the previous API version endpoints accidentally, and there will be nothing in the commit that suggests it.

So instead, what we can do is version our schema definitions:

SchemaTest.define :user, version: 1 do
  id
  string :name
  url :avatar_url
end

SchemaTest.define :user, version: 2 do
  id
  string :first_name
  string :last_name
  url :avatar_url
end

And then use the correct version in each of our tests:

# v1 comment test
get :comment

assert_schema_validates response.body, schema_for(:comment, version: 1)

# v2 comment test
get :comment

assert_schema_validates response.body, schema_for(:comment, version: 2)

This way we can now be confident that our version 1 tests are actually verifying the genuine version 1 schema, and likewise for the version 2 tests.

But there's another hole that we're about to step in...

Nested schemas

To be written

Installation

Add this line to your application's Gemfile:

gem 'schema-test'

And then execute:

$ bundle

Or install it yourself as:

$ gem install schema-test

Usage

Once the gem is in your bundle, add it to your tests:

Minitest with Rails

At or near the top of test_helper.rb add the following:

require 'schema_test/minitest'
SchemaTest.configure do |config|
  config.domain = 'mydomain.com'
  config.definition_paths << Rails.root.join('test', 'schema_definitions')
  config.disable_rubocop = true # optional, set to true if using rubocop to disable it in the generated code
end
SchemaTest.load!

Within class ActiveSupport::TestCase in the same file, add

include SchemaTest::Minitest

Create the directory test/schema_definitions; within this directory (and any subdirectories) you can start to define your schemas. A simple one would be:

SchemaTest.define :user, version: 1 do
  id
  string :name
  url :avatar_url
  integer :follower_count
end

You can then make assertions using this schema in your tests. For example, if you have a controller action that responds with JSON version of a user, you might write a test like this:

test 'JSON returned matches schema' do
  user = User.first
  get :show, params: { id: user.id }

  json = JSON.parse(response.body)
  assert_valid_json_for_schema(json, :user, version: 1)
end

When you run this test, this gem will convert the schema definition you've given into JSON Schema, and then dynamically rewrite your test to verify against the fully-expanded schema. After you run the test, if you reload the file, you should see something like this:

test 'JSON returned matches schema' do
  user = User.first
  get :show, params: { id: user.id }

  json = JSON.parse(response.body)
  assert_valid_json_for_schema( # EXPANDED
    json, 
    {:version=>1,
      :schema=>
      {"$schema"=>"http://json-schema.org/draft-07/schema#",
        "$id"=>"http://mydomain/v1/user.json",
        "title"=>"user",
        "type"=>"object",
        "properties"=>
        {"id"=>{"type"=>"integer"},
          "name"=>{"type"=>"string"},
          "avatar_url"=>{"type"=>"string", "format"=>"uri"},
          "follower_count"=>{"type"=>"integer}},
        "required"=>["id", "name", "avatar_url", "follower_count"],
        "additionalProperties"=>false}}
  ) # END EXPANDED
end

Keeping the full schema directly in the tests means that it is impossible for us to accidentally impact any API endpoints with a distant schema change without also producing some change in the test files for those endpoints. That is the main benefit that this library tries to acheive.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec 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/lazyatom/schema-test. 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 SchemaTest project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.