From b8a9f97fc25f627ab4556a70107b18948f03aa0d Mon Sep 17 00:00:00 2001 From: Mauro George Date: Sat, 29 Aug 2015 12:37:24 -0300 Subject: [PATCH] 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]: https://github.com/rails-api/active_model_serializers/issues/1011#issuecomment-127608121 --- CHANGELOG.md | 1 + active_model_serializers.gemspec | 2 + docs/README.md | 1 + docs/howto/test.md | 78 +++++++++++++++++++ lib/active_model/serializer/assertions.rb | 16 ++++ lib/active_model/serializer/configuration.rb | 1 + lib/active_model_serializers.rb | 2 + test/assertions_test.rb | 46 +++++++++++ .../serializer/assertions_test/my/index.json | 6 ++ .../serializer/assertions_test/my/index.json | 6 ++ .../serializer/assertions_test/my/show.json | 5 ++ test/support/schemas/custom/show.json | 6 ++ 12 files changed, 170 insertions(+) create mode 100644 docs/howto/test.md create mode 100644 lib/active_model/serializer/assertions.rb create mode 100644 test/assertions_test.rb create mode 100644 test/support/custom_schemas/active_model/serializer/assertions_test/my/index.json create mode 100644 test/support/schemas/active_model/serializer/assertions_test/my/index.json create mode 100644 test/support/schemas/active_model/serializer/assertions_test/my/show.json create mode 100644 test/support/schemas/custom/show.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd7a819c..fdd301df6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,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) diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index f557dc975..dc8007179 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -46,6 +46,8 @@ Gem::Specification.new do |spec| # activesupport # activemodel + spec.add_runtime_dependency 'json-schema' + # Soft dependency for pagination spec.add_development_dependency 'kaminari', ' ~> 0.16.3' spec.add_development_dependency 'will_paginate', '~> 3.0', '>= 3.0.7' diff --git a/docs/README.md b/docs/README.md index a20c086e9..7f0a8ac02 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/docs/howto/test.md b/docs/howto/test.md new file mode 100644 index 000000000..98000c632 --- /dev/null +++ b/docs/howto/test.md @@ -0,0 +1,78 @@ +# How to test + +## Test helpers + +AMS 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 a 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 in +`test/support/schemas/posts/show.json` the helper uses a convention to the name +of 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 other 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 a initializer. + +```ruby +ActiveModel::Serializer.config.schema_path = `spec/support/schemas` +``` diff --git a/lib/active_model/serializer/assertions.rb b/lib/active_model/serializer/assertions.rb new file mode 100644 index 000000000..773f0c914 --- /dev/null +++ b/lib/active_model/serializer/assertions.rb @@ -0,0 +1,16 @@ +require 'json-schema' + +module ActiveModel + class Serializer + module Assertions + def assert_response_schema(schema_path = nil) + controller_path = response.request.filtered_parameters[:controller] + action = response.request.filtered_parameters[:action] + schema_directory = ActiveModel::Serializer.config.schema_path + schema_path ||= "#{controller_path}/#{action}.json" + schema_full_path = "#{schema_directory}/#{schema_path}" + JSON::Validator.validate!(schema_full_path, response.body, strict: true) + end + end + end +end diff --git a/lib/active_model/serializer/configuration.rb b/lib/active_model/serializer/configuration.rb index 520f76f20..b8e68d840 100644 --- a/lib/active_model/serializer/configuration.rb +++ b/lib/active_model/serializer/configuration.rb @@ -21,6 +21,7 @@ def config.array_serializer config.adapter = :attributes config.jsonapi_resource_type = :plural + base.config.schema_path = 'test/support/schemas' end end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index a3e2ff006..d482c111f 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -51,6 +51,7 @@ def silence_warnings require 'active_model/serializer' require 'active_model/serializable_resource' require 'active_model/serializer/version' +require 'active_model/serializer/assertions' require 'action_controller/serialization' ActiveSupport.on_load(:action_controller) do @@ -59,6 +60,7 @@ def silence_warnings ActionDispatch::Reloader.to_prepare do ActiveModel::Serializer.serializers_cache.clear end + ActionController::TestCase.send(:include, ActiveModel::Serializer::Assertions) end require 'active_model/serializer/railtie' diff --git a/test/assertions_test.rb b/test/assertions_test.rb new file mode 100644 index 000000000..fb856282a --- /dev/null +++ b/test/assertions_test.rb @@ -0,0 +1,46 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class AssertionsTest < ActionController::TestCase + class MyController < ActionController::Base + def index + render json: Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') + end + + def show + index + end + end + + tests MyController + + def test_that_assert_with_a_valid_schema + get :index + assert_response_schema + end + + def test_that_raises_a_json_schema_with_a_invalid_schema + get :show + assert_raises JSON::Schema::ValidationError do + assert_response_schema + end + end + + def test_that_assert_with_a_custom_schema + get :show + assert_response_schema('custom/show.json') + end + + def test_that_assert_with_a_custom_schema_directory + original_schema_path = ActiveModel::Serializer.config.schema_path + ActiveModel::Serializer.config.schema_path = 'test/support/custom_schemas' + + get :index + assert_response_schema + + ActiveModel::Serializer.config.schema_path = original_schema_path + end + end + end +end diff --git a/test/support/custom_schemas/active_model/serializer/assertions_test/my/index.json b/test/support/custom_schemas/active_model/serializer/assertions_test/my/index.json new file mode 100644 index 000000000..9474c509b --- /dev/null +++ b/test/support/custom_schemas/active_model/serializer/assertions_test/my/index.json @@ -0,0 +1,6 @@ +{ + "properties": { + "name" : { "type" : "string" }, + "description" : { "type" : "string" } + } +} diff --git a/test/support/schemas/active_model/serializer/assertions_test/my/index.json b/test/support/schemas/active_model/serializer/assertions_test/my/index.json new file mode 100644 index 000000000..9474c509b --- /dev/null +++ b/test/support/schemas/active_model/serializer/assertions_test/my/index.json @@ -0,0 +1,6 @@ +{ + "properties": { + "name" : { "type" : "string" }, + "description" : { "type" : "string" } + } +} diff --git a/test/support/schemas/active_model/serializer/assertions_test/my/show.json b/test/support/schemas/active_model/serializer/assertions_test/my/show.json new file mode 100644 index 000000000..269fcda8a --- /dev/null +++ b/test/support/schemas/active_model/serializer/assertions_test/my/show.json @@ -0,0 +1,5 @@ +{ + "properties": { + "name" : { "type" : "string" } + } +} diff --git a/test/support/schemas/custom/show.json b/test/support/schemas/custom/show.json new file mode 100644 index 000000000..9474c509b --- /dev/null +++ b/test/support/schemas/custom/show.json @@ -0,0 +1,6 @@ +{ + "properties": { + "name" : { "type" : "string" }, + "description" : { "type" : "string" } + } +}