diff --git a/CHANGELOG.md b/CHANGELOG.md index b70c2e9a4..b8c4198e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### 0.10.0 + * adds `assert_serializer` test helper [@maurogeorge] * adds adapters pattern * adds support for `meta` and `meta_key` [@kurko] * adds method to override association [@kurko] diff --git a/README.md b/README.md index a5c5528f4..6e72f7e4d 100644 --- a/README.md +++ b/README.md @@ -335,6 +335,34 @@ class PostSerializer < ActiveModel::Serializer end ``` +## 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 +``` + ## Getting Help If you find a bug, please report an [Issue](https://github.com/rails-api/active_model_serializers/issues/new). diff --git a/lib/action_controller/serialization_test_case.rb b/lib/action_controller/serialization_test_case.rb new file mode 100644 index 000000000..ef91f6bcc --- /dev/null +++ b/lib/action_controller/serialization_test_case.rb @@ -0,0 +1,78 @@ +module ActionController + module SerializationAssertions + 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 ->(exp) { exp.kind_of?(Class) && exp < ActiveModel::Serializer } + serializers.include?(expectation.name) + when Symbol + expectation = expectation.to_s.camelize + serializers.include?(expectation) + when String + !expectation.empty? && serializers.include?(expectation) + when Regexp + serializers.any? do |serializer| + serializer.match(expectation) + end + when NilClass + serializers.blank? + else + raise 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 + 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 + end +end diff --git a/lib/active_model/serializable_resource.rb b/lib/active_model/serializable_resource.rb index aa8f139a2..372334489 100644 --- a/lib/active_model/serializable_resource.rb +++ b/lib/active_model/serializable_resource.rb @@ -19,6 +19,7 @@ def initialize(resource, options = {}) # returns the contents of the block def self.serialize(resource, options = {}) serializable_resource = SerializableResource.new(resource, options) + serializable_resource.instrument if block_given? yield serializable_resource else @@ -74,6 +75,14 @@ def serializer? use_adapter? && !!(serializer) end + def instrument + if serializer? + event_name = "serialize.active_model_serializers" + payload = { serializer: serializer.name } + ActiveSupport::Notifications.instrument(event_name, payload) + end + end + private ActiveModelSerializers.silence_warnings do diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 01c23e5f7..f0a58517e 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -19,12 +19,14 @@ def silence_warnings require 'active_model/serializer/railtie' require 'action_controller' require 'action_controller/serialization' + require 'action_controller/serialization_test_case' ActiveSupport.on_load(:action_controller) do include ::ActionController::Serialization ActionDispatch::Reloader.to_prepare do ActiveModel::Serializer.serializers_cache.clear end + ActionController::TestCase.send(:include, ::ActionController::SerializationAssertions) end rescue LoadError # rails not installed, continuing diff --git a/test/action_controller/serialization_test_case_test.rb b/test/action_controller/serialization_test_case_test.rb new file mode 100644 index 000000000..26ef9bd7f --- /dev/null +++ b/test/action_controller/serialization_test_case_test.rb @@ -0,0 +1,70 @@ +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 + + 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 %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 + + 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.