diff --git a/CHANGELOG.md b/CHANGELOG.md index a89c9e05..8ec8a389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,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) +- [#1099](https://github.com/rails-api/active_model_serializers/pull/1099) Adds `assert_serializer` 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/Gemfile b/Gemfile index 9a386356..659f82e0 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,7 @@ version = ENV['RAILS_VERSION'] || '4.2' if version == 'master' gem 'rack', github: 'rack/rack' gem 'arel', github: 'rails/arel' + gem 'rails-controller-testing', github: 'rails/rails-controller-testing' git 'https://github.com/rails/rails.git' do gem 'railties' gem 'activesupport' diff --git a/docs/README.md b/docs/README.md index a20c086e..7f0a8ac0 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 00000000..67cbef1c --- /dev/null +++ b/docs/howto/test.md @@ -0,0 +1,29 @@ +# How to test + +## Test helpers + +ActiveModelSerializers provides a `assert_serializer` method to be used on your controller tests to +assert that a specific serializer was used. + +```ruby +class PostsControllerTest < ActionController::TestCase + test "should render post serializer" do + get :index + assert_serializer "PostSerializer" + # # return a custom error message + # assert_serializer "PostSerializer", "PostSerializer not rendered" + # + # # assert that the instance of PostSerializer was rendered + # assert_serializer PostSerializer + # + # # assert that the "PostSerializer" serializer was rendered + # assert_serializer :post_serializer + # + # # assert that the rendered serializer starts with "Post" + # assert_serializer %r{\APost.+\Z} + # + # # assert that no serializer was rendered + # assert_serializer nil + end +end +``` diff --git a/lib/active_model/serializer/railtie.rb b/lib/active_model/serializer/railtie.rb index 18bb513c..e0af1cad 100644 --- a/lib/active_model/serializer/railtie.rb +++ b/lib/active_model/serializer/railtie.rb @@ -19,5 +19,9 @@ module ActiveModel app.load_generators require 'generators/serializer/resource_override' end + + if Rails.env.test? + ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Serializer) + end end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index b955d7a0..af216516 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -14,6 +14,7 @@ module ActiveModelSerializers autoload :Callbacks autoload :Deserialization autoload :Logging + autoload :Test end require 'active_model/serializer' diff --git a/lib/active_model_serializers/test.rb b/lib/active_model_serializers/test.rb new file mode 100644 index 00000000..77f0b702 --- /dev/null +++ b/lib/active_model_serializers/test.rb @@ -0,0 +1,5 @@ +module ActiveModelSerializers + module Test + autoload :Serializer, 'active_model_serializers/test/serializer' + end +end diff --git a/lib/active_model_serializers/test/serializer.rb b/lib/active_model_serializers/test/serializer.rb new file mode 100644 index 00000000..8e8c9549 --- /dev/null +++ b/lib/active_model_serializers/test/serializer.rb @@ -0,0 +1,122 @@ +module ActiveModelSerializers + module Test + module Serializer + extend ActiveSupport::Concern + + included do + setup :setup_serialization_subscriptions + end + + # Asserts that the request was rendered with the appropriate serializers. + # + # # assert that the "PostSerializer" serializer was rendered + # assert_serializer "PostSerializer" + # + # # return a custom error message + # assert_serializer "PostSerializer", "PostSerializer not rendered" + # + # # assert that the instance of PostSerializer was rendered + # assert_serializer PostSerializer + # + # # assert that the "PostSerializer" serializer was rendered + # assert_serializer :post_serializer + # + # # assert that the rendered serializer starts with "Post" + # assert_serializer %r{\APost.+\Z} + # + # # assert that no serializer was rendered + # assert_serializer nil + # + def assert_serializer(expectation, message = nil) + @assert_serializer.expectation = expectation + @assert_serializer.message = message + @assert_serializer.response = response + assert(@assert_serializer.matches?, @assert_serializer.message) + end + + class AssertSerializer + attr_reader :serializers, :message + attr_accessor :response, :expectation + + def initialize + @serializers = [] + end + + def message=(message) + @message = message || "expecting <#{expectation.inspect}> but rendering with <#{serializers}>" + end + + def matches? + # Force body to be read in case the template is being streamed. + response.body + + case expectation + when a_serializer? + matches_class? + when Symbol + matches_symbol? + when String + matches_string? + when Regexp + matches_regexp? + when NilClass + matches_nil? + else + fail ArgumentError, 'assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil' + end + end + + def subscribe + ActiveSupport::Notifications.subscribe(event_name) do |_name, _start, _finish, _id, payload| + serializer = payload[:serializer].name + serializers << serializer + end + end + + def unsubscribe + ActiveSupport::Notifications.unsubscribe(event_name) + end + + private + + def matches_class? + serializers.include?(expectation.name) + end + + def matches_symbol? + camelize_expectation = expectation.to_s.camelize + serializers.include?(camelize_expectation) + end + + def matches_string? + !expectation.empty? && serializers.include?(expectation) + end + + def matches_regexp? + serializers.any? do |serializer| + serializer.match(expectation) + end + end + + def matches_nil? + serializers.blank? + end + + def a_serializer? + ->(exp) { exp.is_a?(Class) && exp < ActiveModel::Serializer } + end + + def event_name + 'render.active_model_serializers' + end + end + + private + + def setup_serialization_subscriptions + @assert_serializer = AssertSerializer.new + @assert_serializer.subscribe + end + end + end +end diff --git a/test/active_model_serializers/test/serializer_test.rb b/test/active_model_serializers/test/serializer_test.rb new file mode 100644 index 00000000..3a5f0e99 --- /dev/null +++ b/test/active_model_serializers/test/serializer_test.rb @@ -0,0 +1,73 @@ +require 'test_helper' +require 'rails-controller-testing' if Rails::VERSION::MAJOR >= 5 + +module ActiveModelSerializers + module Test + class SerializerTest < ActionController::TestCase + include ActiveModelSerializers::Test::Serializer + + class MyController < ActionController::Base + def render_using_serializer + render json: Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') + end + + def render_text + render text: 'ok' + end + + def render_template + prepend_view_path './test/fixtures' + render template: 'template' + end + end + + tests MyController + + def test_supports_specifying_serializers_with_a_serializer_class + get :render_using_serializer + assert_serializer ProfileSerializer + end + + def test_supports_specifying_serializers_with_a_regexp + get :render_using_serializer + assert_serializer(/\AProfile.+\Z/) + end + + def test_supports_specifying_serializers_with_a_string + get :render_using_serializer + assert_serializer 'ProfileSerializer' + end + + def test_supports_specifying_serializers_with_a_symbol + get :render_using_serializer + assert_serializer :profile_serializer + end + + def test_supports_specifying_serializers_with_a_nil + get :render_text + assert_serializer nil + end + + def test_raises_descriptive_error_message_when_serializer_was_not_rendered + get :render_using_serializer + e = assert_raise ActiveSupport::TestCase::Assertion do + assert_serializer 'PostSerializer' + end + assert_match 'expecting <"PostSerializer"> but rendering with <["ProfileSerializer"]>', e.message + end + + def test_raises_argument_error_when_asserting_with_invalid_object + get :render_using_serializer + e = assert_raise ArgumentError do + assert_serializer Hash + end + assert_match 'assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil', e.message + end + + def test_does_not_overwrite_notification_subscriptions + get :render_template + assert_template 'template' + end + end + end +end diff --git a/test/fixtures/template.html.erb b/test/fixtures/template.html.erb new file mode 100644 index 00000000..1f87be87 --- /dev/null +++ b/test/fixtures/template.html.erb @@ -0,0 +1 @@ +
Hello.