diff --git a/lib/action_controller/serialization_test_case.rb b/lib/action_controller/serialization_test_case.rb new file mode 100644 index 00000000..37c882b5 --- /dev/null +++ b/lib/action_controller/serialization_test_case.rb @@ -0,0 +1,79 @@ +module ActionController + module SerializationAssertions + extend ActiveSupport::Concern + + included do + setup :setup_subscriptions + teardown :teardown_subscriptions + end + + def setup_subscriptions + @serializers = Hash.new(0) + + ActiveSupport::Notifications.subscribe("!serialize.active_model_serializers") do |name, start, finish, id, payload| + serializer = payload[:serializer] + @serializers[serializer] += 1 + end + end + + def teardown_subscriptions + ActiveSupport::Notifications.unsubscribe("!serialize.active_model_serializers") + end + + def process(*args) + @serializers = Hash.new(0) + super + end + + # Asserts that the request was rendered with the appropriate serializers. + # + # # assert that the "PostSerializer" serializer was rendered + # assert_serializer "PostSerializer" + # + # # 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(options = {}, message = nil) + # Force body to be read in case the template is being streamed. + response.body + + rendered = @serializers + msg = message || "expecting <#{options.inspect}> but rendering with <#{rendered.keys}>" + + matches_serializer = case options + when lambda { |options| options.kind_of?(Class) && options < ActiveModel::Serializer } + rendered.any? do |serializer, count| + options.name == serializer + end + when Symbol + options = options.to_s.camelize + rendered.any? do |serializer, count| + serializer == options + end + when String + !options.empty? && rendered.any? do |serializer, count| + serializer == options + end + when Regexp + rendered.any? do |serializer, count| + serializer.match(options) + end + when NilClass + rendered.blank? + else + raise ArgumentError, "assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil" + end + assert matches_serializer, msg + end + end +end diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index b314dbfc..4bb5c2a3 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -54,5 +54,10 @@ module ActiveModel end end end + + private + def instrumentation_keys + [:object, :scope, :root, :meta_key, :meta, :each_serializer, :resource_name, :key_format] + end end end diff --git a/lib/active_model/default_serializer.rb b/lib/active_model/default_serializer.rb index 5ab78c3b..c2db6b1d 100644 --- a/lib/active_model/default_serializer.rb +++ b/lib/active_model/default_serializer.rb @@ -15,11 +15,18 @@ module ActiveModel end def as_json(options={}) - return [] if @object.nil? && @wrap_in_array - hash = @object.as_json - @wrap_in_array ? [hash] : hash + instrument('!serialize') do + return [] if @object.nil? && @wrap_in_array + hash = @object.as_json + @wrap_in_array ? [hash] : hash + end end alias serializable_hash as_json alias serializable_object as_json + + private + def instrumentation_keys + [:object, :wrap_in_array] + end end end diff --git a/lib/active_model/serializable.rb b/lib/active_model/serializable.rb index 7a34bc04..e70a9afc 100644 --- a/lib/active_model/serializable.rb +++ b/lib/active_model/serializable.rb @@ -1,12 +1,14 @@ module ActiveModel module Serializable def as_json(options={}) - if root = options.fetch(:root, json_key) - hash = { root => serializable_object } - hash.merge!(serializable_data) - hash - else - serializable_object + instrument('!serialize') do + if root = options.fetch(:root, json_key) + hash = { root => serializable_object } + hash.merge!(serializable_data) + hash + else + serializable_object + end end end @@ -21,5 +23,18 @@ module ActiveModel def embedded_in_root_associations {} end + + private + def instrument(action, &block) + payload = instrumentation_keys.inject({ serializer: self.class.name }) do |payload, key| + payload[:payload] = self.instance_variable_get(:"@#{key}") + payload + end + ActiveSupport::Notifications.instrument("#{action}.active_model_serializers", payload, &block) + end + + def instrumentation_keys + [:object, :scope, :root, :meta_key, :meta, :wrap_in_array, :only, :except, :key_format] + end end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 80ce3470..01b2ed83 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -7,10 +7,12 @@ require 'active_model/serializer/railtie' if defined?(Rails) begin require 'action_controller' require 'action_controller/serialization' + require 'action_controller/serialization_test_case' ActiveSupport.on_load(:after_initialize) do if ::ActionController::Serialization.enabled ActionController::Base.send(:include, ::ActionController::Serialization) + ActionController::TestCase.send(:include, ::ActionController::SerializationAssertions) end end rescue LoadError diff --git a/test/integration/action_controller/serialization_test_case_test.rb b/test/integration/action_controller/serialization_test_case_test.rb new file mode 100644 index 00000000..9532a88b --- /dev/null +++ b/test/integration/action_controller/serialization_test_case_test.rb @@ -0,0 +1,61 @@ +require 'test_helper' + +module ActionController + module SerializationsAssertions + class RenderSerializerTest < 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 + 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 %r{\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 + end + end +end