Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Create assert_response_schema test helper #1270

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Features:
CollectionSerializer for clarity, add ActiveModelSerializers.config.collection_serializer (@bf4)
- [#1295](https://github.com/rails-api/active_model_serializers/pull/1295) Add config `serializer_lookup_enabled` that,
when disabled, requires serializers to explicitly specified. (@trek)
- [#1270](https://github.com/rails-api/active_model_serializers/pull/1270) Adds `assert_response_schema` test helper (@maurogeorge)

Fixes:
- [#1239](https://github.com/rails-api/active_model_serializers/pull/1239) Fix duplicates in JSON API compound documents (@beauby)
Expand Down
1 change: 1 addition & 0 deletions active_model_serializers.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'timecop', '~> 0.7'
spec.add_development_dependency 'minitest-reporters'
spec.add_development_dependency 'grape', ['>= 0.13', '< 1.0']
spec.add_development_dependency 'json_schema'
end
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10.
- [How to add root key](howto/add_root_key.md)
- [How to add pagination links](howto/add_pagination_links.md)
- [Using ActiveModelSerializers Outside Of Controllers](howto/outside_controller_use.md)
- [Testing ActiveModelSerializers](howto/test.md)

## Integrations

Expand Down
132 changes: 132 additions & 0 deletions docs/howto/test.md
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invalid json :trollface:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄


{
"properties": {
"name": {
"$ref": "file://attributes.json#/properties/name"
},
"description": {
"$ref": "file://attributes.json#/properties/description"
}
}
}
```
1 change: 1 addition & 0 deletions lib/active_model/serializer/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def config.array_serializer

config.adapter = :attributes
config.jsonapi_resource_type = :plural
config.schema_path = 'test/support/schemas'
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/active_model/serializer/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ class Railtie < Rails::Railtie
app.load_generators
require 'generators/serializer/resource_override'
end

if Rails.env.test?
ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Schema)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too bad include is still private in Ruby 2.0 :(

end
end
end
1 change: 1 addition & 0 deletions lib/active_model_serializers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def self.config
autoload :Model
autoload :Callbacks
autoload :Logging
autoload :Test
end

require 'active_model/serializer'
Expand Down
6 changes: 6 additions & 0 deletions lib/active_model_serializers/test.rb
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
103 changes: 103 additions & 0 deletions lib/active_model_serializers/test/schema.rb
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind adding comments to the effect of what nil schema_path/message mean and what types of data they are? Yardoc or pseudo-yardoc is preferred.

# A Minitest Assertion that the response is valid per the schema
# which uses the given error message
# @params schema_path [String] blah nil blah else
# @params message [String] blah nil blah else
# @example
#   get :something
#   assert_response_schema

and the matcher just returns bool?

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
Loading