Skip to content

Commit ac13053

Browse files
committed
Merge branch 'maurogeorge-patch-09'
Rebased from cb3afa9 to 9aed6ac and resolved conflicts by Benjamin Fleischer (bf4)
2 parents 9aed6ac + 5058694 commit ac13053

File tree

15 files changed

+543
-2
lines changed

15 files changed

+543
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Breaking changes:
1616

1717
Features:
1818

19+
- [#1270](https://github.com/rails-api/active_model_serializers/pull/1270) Adds `assert_response_schema` test helper (@maurogeorge)
20+
- [#1099](https://github.com/rails-api/active_model_serializers/pull/1099) Adds `assert_serializer` test helper (@maurogeorge)
1921
- [#1403](https://github.com/rails-api/active_model_serializers/pull/1403) Add support for if/unless on attributes/associations (@beauby)
2022
- [#1248](https://github.com/rails-api/active_model_serializers/pull/1248) Experimental: Add support for JSON API deserialization (@beauby)
2123
- [#1378](https://github.com/rails-api/active_model_serializers/pull/1378) Change association blocks
@@ -45,7 +47,6 @@ Features:
4547
CollectionSerializer for clarity, add ActiveModelSerializers.config.collection_serializer (@bf4)
4648
- [#1295](https://github.com/rails-api/active_model_serializers/pull/1295) Add config `serializer_lookup_enabled` that,
4749
when disabled, requires serializers to explicitly specified. (@trek)
48-
- [#1099](https://github.com/rails-api/active_model_serializers/pull/1099) Adds `assert_serializer` test helper (@maurogeorge)
4950

5051
Fixes:
5152
- [#1239](https://github.com/rails-api/active_model_serializers/pull/1239) Fix duplicates in JSON API compound documents (@beauby)

active_model_serializers.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,5 @@ Gem::Specification.new do |spec|
5454
spec.add_development_dependency 'timecop', '~> 0.7'
5555
spec.add_development_dependency 'minitest-reporters'
5656
spec.add_development_dependency 'grape', ['>= 0.13', '< 1.0']
57+
spec.add_development_dependency 'json_schema'
5758
end

docs/howto/test.md

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# How to test
22

3-
## Test helpers
3+
## Controller Serializer Usage
44

55
ActiveModelSerializers provides a `assert_serializer` method to be used on your controller tests to
66
assert that a specific serializer was used.
@@ -16,3 +16,137 @@ end
1616

1717
See [ActiveModelSerializers::Test::Serializer](../../lib/active_model_serializers/test/serializer.rb)
1818
for more examples and documentation.
19+
20+
## Serialization against a schema
21+
22+
### Dependencies
23+
24+
To use the `assert_response_schema` you need to have the
25+
[`json_schema`](https://github.com/brandur/json_schema) on your Gemfile. Please
26+
add it to your Gemfile and run `$ bundle install`.
27+
28+
### Minitest test helpers
29+
30+
ActiveModelSerializers provides a `assert_response_schema` method to be used on your controller tests to
31+
assert the response against a [JSON Schema](http://json-schema.org/). Let's take
32+
a look in an example.
33+
34+
```ruby
35+
class PostsController < ApplicationController
36+
def show
37+
@post = Post.find(params[:id])
38+
39+
render json: @post
40+
end
41+
end
42+
```
43+
44+
To test the `posts#show` response of this controller we need to create a file
45+
named `test/support/schemas/posts/show.json`. The helper uses a naming convention
46+
to locate the file.
47+
48+
This file is a JSON Schema representation of our response.
49+
50+
```json
51+
{
52+
"properties": {
53+
"title" : { "type" : "string" },
54+
"content" : { "type" : "string" }
55+
}
56+
}
57+
```
58+
59+
With all in place we can go to our test and use the helper.
60+
61+
```ruby
62+
class PostsControllerTest < ActionController::TestCase
63+
test "should render right response" do
64+
get :index
65+
assert_response_schema
66+
end
67+
end
68+
```
69+
70+
#### Load a custom schema
71+
72+
If we need to use another schema, for example when we have a namespaced API that
73+
shows the same response, we can pass the path of the schema.
74+
75+
```ruby
76+
module V1
77+
class PostsController < ApplicationController
78+
def show
79+
@post = Post.find(params[:id])
80+
81+
render json: @post
82+
end
83+
end
84+
end
85+
```
86+
87+
```ruby
88+
class V1::PostsControllerTest < ActionController::TestCase
89+
test "should render right response" do
90+
get :index
91+
assert_response_schema('posts/show.json')
92+
end
93+
end
94+
```
95+
96+
#### Change the schema path
97+
98+
By default all schemas are created at `test/support/schemas`. If we are using
99+
RSpec for example we can change this to `spec/support/schemas` defining the
100+
default schema path in an initializer.
101+
102+
```ruby
103+
ActiveModelSerializers.config.schema_path = 'spec/support/schemas'
104+
```
105+
106+
#### Using with the Heroku’s JSON Schema-based tools
107+
108+
To use the test helper with the [prmd](https://github.com/interagent/prmd) and
109+
[committee](https://github.com/interagent/committee).
110+
111+
We need to change the schema path to the recommended by prmd:
112+
113+
```ruby
114+
ActiveModelSerializers.config.schema_path = 'docs/schema/schemata'
115+
```
116+
117+
We also need to structure our schemata according to Heroku's conventions
118+
(e.g. including
119+
[required metadata](https://github.com/interagent/prmd/blob/master/docs/schemata.md#meta-data)
120+
and [links](https://github.com/interagent/prmd/blob/master/docs/schemata.md#links).
121+
122+
#### JSON Pointers
123+
124+
If we plan to use [JSON
125+
Pointers](http://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf) we need to define the `id` attribute on the schema. Example:
126+
127+
```js
128+
// attributes.json
129+
130+
{
131+
"id": "file://attributes.json#",
132+
"properties": {
133+
"name" : { "type" : "string" },
134+
"description" : { "type" : "string" }
135+
}
136+
}
137+
```
138+
139+
```js
140+
// show.json
141+
142+
{
143+
"properties": {
144+
"name": {
145+
"$ref": "file://attributes.json#/properties/name"
146+
},
147+
"description": {
148+
"$ref": "file://attributes.json#/properties/description"
149+
}
150+
}
151+
}
152+
```

lib/active_model/serializer/configuration.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def config.array_serializer
2121

2222
config.adapter = :attributes
2323
config.jsonapi_resource_type = :plural
24+
config.schema_path = 'test/support/schemas'
2425
end
2526
end
2627
end

lib/active_model/serializer/railtie.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Railtie < Rails::Railtie
2121
end
2222

2323
if Rails.env.test?
24+
ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Schema)
2425
ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Serializer)
2526
end
2627
end

lib/active_model_serializers/test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ module ActiveModelSerializers
22
module Test
33
extend ActiveSupport::Autoload
44
autoload :Serializer
5+
autoload :Schema
56
end
67
end
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
module ActiveModelSerializers
2+
module Test
3+
module Schema
4+
# A Minitest Assertion that test the response is valid against a schema.
5+
# @params schema_path [String] a custom schema path
6+
# @params message [String] a custom error message
7+
# @return [Boolean] true when the response is valid
8+
# @return [Minitest::Assertion] when the response is invalid
9+
# @example
10+
# get :index
11+
# assert_response_schema
12+
def assert_response_schema(schema_path = nil, message = nil)
13+
matcher = AssertResponseSchema.new(schema_path, response, message)
14+
assert(matcher.call, matcher.message)
15+
end
16+
17+
MissingSchema = Class.new(Errno::ENOENT)
18+
InvalidSchemaError = Class.new(StandardError)
19+
20+
class AssertResponseSchema
21+
attr_reader :schema_path, :response, :message
22+
23+
def initialize(schema_path, response, message)
24+
require_json_schema!
25+
@response = response
26+
@schema_path = schema_path || schema_path_default
27+
@message = message
28+
@document_store = JsonSchema::DocumentStore.new
29+
add_schema_to_document_store
30+
end
31+
32+
def call
33+
json_schema.expand_references!(store: document_store)
34+
status, errors = json_schema.validate(response_body)
35+
@message ||= errors.map(&:to_s).to_sentence
36+
status
37+
end
38+
39+
protected
40+
41+
attr_reader :document_store
42+
43+
def controller_path
44+
response.request.filtered_parameters[:controller]
45+
end
46+
47+
def action
48+
response.request.filtered_parameters[:action]
49+
end
50+
51+
def schema_directory
52+
ActiveModelSerializers.config.schema_path
53+
end
54+
55+
def schema_full_path
56+
"#{schema_directory}/#{schema_path}"
57+
end
58+
59+
def schema_path_default
60+
"#{controller_path}/#{action}.json"
61+
end
62+
63+
def schema_data
64+
load_json_file(schema_full_path)
65+
end
66+
67+
def response_body
68+
load_json(response.body)
69+
end
70+
71+
def json_schema
72+
@json_schema ||= JsonSchema.parse!(schema_data)
73+
end
74+
75+
def add_schema_to_document_store
76+
Dir.glob("#{schema_directory}/**/*.json").each do |path|
77+
schema_data = load_json_file(path)
78+
extra_schema = JsonSchema.parse!(schema_data)
79+
document_store.add_schema(extra_schema)
80+
end
81+
end
82+
83+
def load_json(json)
84+
JSON.parse(json)
85+
rescue JSON::ParserError => ex
86+
raise InvalidSchemaError, ex.message
87+
end
88+
89+
def load_json_file(path)
90+
load_json(File.read(path))
91+
rescue Errno::ENOENT
92+
raise MissingSchema, "No Schema file at #{schema_full_path}"
93+
end
94+
95+
def require_json_schema!
96+
require 'json_schema'
97+
rescue LoadError
98+
raise LoadError, "You don't have json_schema installed in your application. Please add it to your Gemfile and run bundle install"
99+
end
100+
end
101+
end
102+
end
103+
end

0 commit comments

Comments
 (0)