diff --git a/CHANGELOG.md b/CHANGELOG.md index 43ecc4189..5cbd0a731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Features: - [#1127](https://github.com/rails-api/active_model_serializers/pull/1127) Add support for nested associations for JSON and Attributes adapters via the `include` option (@NullVoxPopuli, @beauby). - [#1050](https://github.com/rails-api/active_model_serializers/pull/1050) Add support for toplevel jsonapi member (@beauby, @bf4) +- [#1099](https://github.com/rails-api/active_model_serializers/pull/1099) Adds `assert_serializer` test helper (@maurogeorge) Fixes: - [#1214](https://github.com/rails-api/active_model_serializers/pull/1214) retrieve the key from the reflection options when building associations (@NullVoxPopuli, @hut8) diff --git a/docs/README.md b/docs/README.md index f0b4803f2..99c6e8723 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,7 @@ This is the documentation of AMS, it's focused on the **0.10.x version.** - [How to add root key](howto/add_root_key.md) - [How to add pagination links](howto/add_pagination_links.md) - [Using AMS Outside Of Controllers](howto/outside_controller_use.md) +- [Testing AMS](howto/test.md) ## Getting Help diff --git a/docs/howto/test.md b/docs/howto/test.md new file mode 100644 index 000000000..88cb9fed6 --- /dev/null +++ b/docs/howto/test.md @@ -0,0 +1,29 @@ +# How to test + +## Test helpers + +AMS 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/serializable_resource.rb b/lib/active_model/serializable_resource.rb index 0791385bc..64b0ca553 100644 --- a/lib/active_model/serializable_resource.rb +++ b/lib/active_model/serializable_resource.rb @@ -9,6 +9,7 @@ def initialize(resource, options = {}) @resource = resource @adapter_opts, @serializer_opts = options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] } + notify_active_support end delegate :serializable_hash, :as_json, :to_json, to: :adapter @@ -61,6 +62,13 @@ def serializer? use_adapter? && !!(serializer) end + def notify_active_support + return unless serializer? + event_name = 'serialize.active_model_serializers' + payload = { serializer: serializer.name } + ActiveSupport::Notifications.instrument(event_name, payload) + end + private ActiveModelSerializers.silence_warnings do diff --git a/lib/active_model/serializer/assertions.rb b/lib/active_model/serializer/assertions.rb new file mode 100644 index 000000000..c86824e11 --- /dev/null +++ b/lib/active_model/serializer/assertions.rb @@ -0,0 +1,104 @@ +module ActiveModel + class Serializer + module Assertions + extend ActiveSupport::Concern + + included do + setup :setup_serialization_subscriptions + teardown :teardown_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) + # Force body to be read in case the template is being streamed. + response.body + + msg = message || "expecting <#{expectation.inspect}> but rendering with <#{serializers}>" + + matches_serializer = case expectation + when a_serializer? + matches_class?(expectation) + when Symbol + matches_symbol?(expectation) + when String + matches_string?(expectation) + when Regexp + matches_regexp?(expectation) + when NilClass + matches_nil? + else + fail ArgumentError, 'assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil' + end + assert(matches_serializer, msg) + end + + private + + ActiveModelSerializers.silence_warnings do + attr_reader :serializers, :expectation + end + + def setup_serialization_subscriptions + @serializers = [] + ActiveSupport::Notifications.subscribe(event_name) do |_name, _start, _finish, _id, payload| + serializer = payload[:serializer] + serializers << serializer + end + end + + def teardown_serialization_subscriptions + ActiveSupport::Notifications.unsubscribe(event_name) + end + + def event_name + 'serialize.active_model_serializers' + end + + def matches_class?(expectation) + serializers.include?(expectation.name) + end + + def matches_symbol?(expectation) + expectation = expectation.to_s.camelize + serializers.include?(expectation) + end + + def matches_string?(expectation) + !expectation.empty? && serializers.include?(expectation) + end + + def matches_regexp?(expectation) + 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 + end + end +end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 6f00620fc..b991db3c6 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -42,6 +42,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 @@ -49,6 +50,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..033394a2c --- /dev/null +++ b/test/assertions_test.rb @@ -0,0 +1,70 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class AssertionsTest < ActionController::TestCase + 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 000000000..1f87be87c --- /dev/null +++ b/test/fixtures/template.html.erb @@ -0,0 +1 @@ +
Hello.