forked from rails-api/active_model_serializers
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create assert_response_schema test helper
It is a common pattern to use JSON Schema to validate a API response[1], [2] and [3]. This patch creates the `assert_response_schema` test helper that helps people do this kind of validation easily on the controller tests. [1]: https://robots.thoughtbot.com/validating-json-schemas-with-an-rspec-matcher [2]: https://github.com/sharethrough/json-schema-rspec [3]: rails-api#1011 (comment)
- Loading branch information
1 parent
7d4f0c5
commit cb3afa9
Showing
17 changed files
with
549 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
# How to test | ||
|
||
## Dependencies | ||
|
||
To use the `assert_response_schema` you need to have the | ||
[`json_schema`](https://github.com/brandur/json_schema) on your Gemfile. Please | ||
add it to your Gemfile and run `$ bundle install`. | ||
|
||
## Minitest test helpers | ||
|
||
ActiveModelSerializers provides a `assert_response_schema` method to be used on your controller tests to | ||
assert the response against a [JSON Schema](http://json-schema.org/). Let's take | ||
a look in an example. | ||
|
||
```ruby | ||
class PostsController < ApplicationController | ||
def show | ||
@post = Post.find(params[:id]) | ||
|
||
render json: @post | ||
end | ||
end | ||
``` | ||
|
||
To test the `posts#show` response of this controller we need to create a file | ||
named `test/support/schemas/posts/show.json`. The helper uses a naming convention | ||
to locate the file. | ||
|
||
This file is a JSON Schema representation of our response. | ||
|
||
```json | ||
{ | ||
"properties": { | ||
"title" : { "type" : "string" }, | ||
"content" : { "type" : "string" } | ||
} | ||
} | ||
``` | ||
|
||
With all in place we can go to our test and use the helper. | ||
|
||
```ruby | ||
class PostsControllerTest < ActionController::TestCase | ||
test "should render right response" do | ||
get :index | ||
assert_response_schema | ||
end | ||
end | ||
``` | ||
|
||
### Load a custom schema | ||
|
||
If we need to use another schema, for example when we have a namespaced API that | ||
shows the same response, we can pass the path of the schema. | ||
|
||
```ruby | ||
module V1 | ||
class PostsController < ApplicationController | ||
def show | ||
@post = Post.find(params[:id]) | ||
|
||
render json: @post | ||
end | ||
end | ||
end | ||
``` | ||
|
||
```ruby | ||
class V1::PostsControllerTest < ActionController::TestCase | ||
test "should render right response" do | ||
get :index | ||
assert_response_schema('posts/show.json') | ||
end | ||
end | ||
``` | ||
### Change the schema path | ||
|
||
By default all schemas are created at `test/support/schemas`. If we are using | ||
RSpec for example we can change this to `spec/support/schemas` defining the | ||
default schema path in an initializer. | ||
|
||
```ruby | ||
ActiveModelSerializers.config.schema_path = 'spec/support/schemas' | ||
``` | ||
|
||
### Using with the Heroku’s JSON Schema-based tools | ||
|
||
To use the test helper with the [prmd](https://github.com/interagent/prmd) and | ||
[committee](https://github.com/interagent/committee). | ||
|
||
We need to change the schema path to the recommended by prmd: | ||
|
||
```ruby | ||
ActiveModelSerializers.config.schema_path = 'docs/schema/schemata' | ||
``` | ||
|
||
We also need to structure our schemata according to Heroku's conventions | ||
(e.g. including | ||
[required metadata](https://github.com/interagent/prmd/blob/master/docs/schemata.md#meta-data) | ||
and [links](https://github.com/interagent/prmd/blob/master/docs/schemata.md#links). | ||
|
||
### JSON Pointers | ||
|
||
If we plan to use [JSON | ||
Pointers](http://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf) we need to define the `id` attribute on the schema. Example: | ||
|
||
```json | ||
# attributes.json | ||
|
||
{ | ||
"id": "file://attributes.json#", | ||
"properties": { | ||
"name" : { "type" : "string" }, | ||
"description" : { "type" : "string" } | ||
} | ||
} | ||
``` | ||
|
||
```json | ||
# show.json | ||
|
||
{ | ||
"properties": { | ||
"name": { | ||
"$ref": "file://attributes.json#/properties/name" | ||
}, | ||
"description": { | ||
"$ref": "file://attributes.json#/properties/description" | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module ActiveModelSerializers | ||
module Test | ||
extend ActiveSupport::Autoload | ||
autoload :Schema | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
module ActiveModelSerializers | ||
module Test | ||
module Schema | ||
# A Minitest Assertion that test the response is valid against a schema. | ||
# @params schema_path [String] a custom schema path | ||
# @params message [String] a custom error message | ||
# @return [Boolean] true when the response is valid | ||
# @return [Minitest::Assertion] when the response is invalid | ||
# @example | ||
# get :index | ||
# assert_response_schema | ||
def assert_response_schema(schema_path = nil, message = nil) | ||
matcher = AssertResponseSchema.new(schema_path, response, message) | ||
assert(matcher.call, matcher.message) | ||
end | ||
|
||
MissingSchema = Class.new(Errno::ENOENT) | ||
InvalidSchemaError = Class.new(StandardError) | ||
|
||
class AssertResponseSchema | ||
attr_reader :schema_path, :response, :message | ||
|
||
def initialize(schema_path, response, message) | ||
require_json_schema! | ||
@response = response | ||
@schema_path = schema_path || schema_path_default | ||
@message = message | ||
@document_store = JsonSchema::DocumentStore.new | ||
add_schema_to_document_store | ||
end | ||
|
||
def call | ||
json_schema.expand_references!(store: document_store) | ||
status, errors = json_schema.validate(response_body) | ||
@message ||= errors.map(&:to_s).to_sentence | ||
status | ||
end | ||
|
||
protected | ||
|
||
attr_reader :document_store | ||
|
||
def controller_path | ||
response.request.filtered_parameters[:controller] | ||
end | ||
|
||
def action | ||
response.request.filtered_parameters[:action] | ||
end | ||
|
||
def schema_directory | ||
ActiveModelSerializers.config.schema_path | ||
end | ||
|
||
def schema_full_path | ||
"#{schema_directory}/#{schema_path}" | ||
end | ||
|
||
def schema_path_default | ||
"#{controller_path}/#{action}.json" | ||
end | ||
|
||
def schema_data | ||
load_json_file(schema_full_path) | ||
end | ||
|
||
def response_body | ||
load_json(response.body) | ||
end | ||
|
||
def json_schema | ||
@json_schema ||= JsonSchema.parse!(schema_data) | ||
end | ||
|
||
def add_schema_to_document_store | ||
Dir.glob("#{schema_directory}/**/*.json").each do |path| | ||
schema_data = load_json_file(path) | ||
extra_schema = JsonSchema.parse!(schema_data) | ||
document_store.add_schema(extra_schema) | ||
end | ||
end | ||
|
||
def load_json(json) | ||
JSON.parse(json) | ||
rescue JSON::ParserError => ex | ||
raise InvalidSchemaError, ex.message | ||
end | ||
|
||
def load_json_file(path) | ||
load_json(File.read(path)) | ||
rescue Errno::ENOENT | ||
raise MissingSchema, "No Schema file at #{schema_full_path}" | ||
end | ||
|
||
def require_json_schema! | ||
require 'json_schema' | ||
rescue LoadError | ||
raise LoadError, "You don't have json_schema installed in your application. Please add it to your Gemfile and run bundle install" | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.