使用Rspec测试ActiveModel::Serializer类

39

考虑以下ActiveModel::Serializer类:

class SampleSerializer < ActiveModel::Serializer
  attributes :id, :name
end

如何使用 RSpec 进行测试?

5个回答

33

前提条件

本答案假设您已经安装并配置了rspec-railsactive_model_serializersfactory_girl_rails三个gem包。

本答案还假设您已经为Sample资源定义了一个工厂。

序列化器规范

在撰写本文时,当前版本(0.10.0.rc3)的active_model_serializers不会接收to_json方法,而是被包装在适配器类中。要获取包装在序列化器实例中的模型的序列化信息,必须创建一个适配器实例:

before(:each) do
  # Create an instance of the model
  @sample = FactoryGirl.build(:sample)

  # Create a serializer instance
  @serializer = SampleSerializer.new(@sample)

  # Create a serialization based on the configured adapter
  @serialization = ActiveModelSerializers::Adapter.create(@serializer)
end
适配器实例接收to_json方法并返回模型的序列化。
subject { JSON.parse(@serialization.to_json) }

可以在返回的JSON上运行期望值。

it 'should have a name that matches' do
  expect(subject['name']).to eql(@sample.name)
end

在解析JSON响应时,必须考虑适配器配置:

  • 默认配置 :attributes 会生成一个没有根键的JSON响应:

subject { JSON.parse(@serialization.to_json) }
  • :json配置会生成一个JSON响应,根键基于模型名称:

  • subject { JSON.parse(@serialization.to_json)['sample'] }
    
    :json_api 配置生成符合 jsonapi 标准的 JSON。
    subject { JSON.parse(@serialization.to_json)['data']['attributes'] }
    

    这太棒了。不知道你能否帮我解决一个问题。我有一个多态序列化器,但在过去的几个小时里,我一直无法使用你上面概述的步骤来呈现相关记录。如果我只是使用SomeSerializer.new(resource).associations,似乎可以看到它们,但这并不是一个序列化数据包。谢谢。 - cgmckeever
    5
    注:ActiveModel::Serializer::Adapter.create 已经被弃用,请使用 ActiveModelSerializers::Adapter.create。 - Terry Ray
    我应该在哪个文件中编写测试? - Arnold Roa

    30

    使用 active_model_serializers 时,只需在序列化器上调用 serializable_hash 方法即可实现更简单的方式:

    it 'should include a correct name' do
      sample = FactoryBot.create(:sample)
      serializer = SampleSerializer.new(sample)
      expect(serializer.serializable_hash[:name]).to eq 'Heisenberg'
    end
    

    只是一个干净的解决方案。 - mmike
    但它没有注意根节点(类型)。 - Piioo

    20

    @gnerkus的回答帮助我引导了自己的实现,但我选择了不同的方法。在ActiveModel::Serializer没有进行任何额外处理的情况下测试返回值似乎既测试了特定键的存在性,也测试了ActiveModel::Serializer是否工作。为了避免测试ActiveModel::Serializer而代之以测试特定键是否存在,这里是我如何测试给定序列化器的方法:

    describe SampleSerializer do
      subject {  SampleSerializer.new(sample) }
    
      it "includes the expected attributes" do
        expect(subject.attributes.keys).
          to contain_exactly(
            :sample_key,
            :another_sample_key
          )
      end
    
      def sample
        @sample ||= build(:sample)
      end
    end
    

    请注意使用contain_exactly:这可以确保除你指定的键之外没有其他键存在。如果使用include,则会导致测试未能检测到包含意外属性的情况而不会失败。当你更新属性但未能更新测试时,此方法可以很好地进行扩展,因为测试将抛出错误并强制你保持一切都是最新的。

    唯一例外的是,当您想要测试已添加到给定序列化器中的自定义方法时,您需要测试方法返回的值/受该方法影响的值,我强烈建议写一个针对该方法的测试。

    更新

    要测试关系,您需要对序列化器进行一些设置。对于简单的序列化器,我避免使用这种设置,但是通过修改设置可以帮助您测试链接、关系等是否存在。

    describe SampleSerializer do
      subject do
        ActiveModelSerializers::Adapter.create(sample_serializer)
      end
    
      it "includes the expected attributes" do
        expect(subject_json(subject)["data"]["attributes"].keys).
          to contain_exactly(
            "date"
          )
      end
    
      it "includes the related Resources" do
        expect(subject_json(subject)["data"]["relationships"].keys).
          to contain_exactly(
            "other-resources"
          )
      end
    
      def subject_json(subject)
        JSON.parse(subject.to_json)
      end
    
      def sample_resource
        @sample_resource ||= build(:sample_resource)
      end
    
      def sample_serializer
        @sample_serializer ||=
          SampleSerializer.new(sample_resource)
      end
    end
    

    1
    这太棒了,而且运行得非常好。感谢@luke的答案。 - tralston
    @Luke,你能否请看一下我的问题:https://stackoverflow.com/questions/50781617/how-to-write-unit-test-using-rspec-for-serializer-with-custom-attributes - awsm sid
    这个解决方案是我见过的最好的。 - kevinluo201

    8

    例子:您可以以现代风格编写此内容。

    分类序列化器:

    class CategorySerializer < ActiveModel::Serializer
      attributes :id, :name
    end
    

    RSpec:
    require 'rails_helper'
    
    RSpec.describe CategorySerializer, type: :serializer do
      let(:category) { FactoryGirl.build(:category) }
      let(:serializer) { described_class.new(category) }
      let(:serialization) { ActiveModelSerializers::Adapter.create(serializer) }
    
      let(:subject) { JSON.parse(serialization.to_json) }
    
      it 'has an id that matches' do
        expect(subject['id']).to eql(category.id)
      end
    
      it 'has a name that matches' do
        expect(subject['name']).to eql(category.name)
      end  
    end
    

    嗨@Tanbir Hasan,你能否帮我看一下这个问题https://stackoverflow.com/questions/50781617/how-to-write-unit-test-using-rspec-for-serializer-with-custom-attributes?谢谢。 - awsm sid
    在版本0.10.10中,current_user在序列化器中可用。要使其在测试中起作用,您需要添加let(:serializer) { described_class.new(category, scope: user, scope_name: :current_user) } - Piioo

    1
    你可以使用subject { described_class.new(user).serializable_hash }来创建序列化对象。 下面是我的示例。
    用户序列化器:
    # frozen_string_literal: true
    
    class UserSerializer < ApplicationSerializer
      attributes :first_name, :last_name, :verification, :avatar_url, :state, :payin_ability
    end
    

    Rspec
    # frozen_string_literal: true
    
    RSpec.describe UserSerializer, type: :serializer do
      let(:user) { create(:user) }
    
      describe '.serializable_hash' do
        subject { described_class.new(user).serializable_hash }
    
        it { expect(subject).to include(:first_name, :last_name, :verification, :avatar_url, :state, :payin_ability) }
    
        it 'returns correct keys and values' do
          expect(subject).to include(
            first_name: be_a(String),
            last_name: be_a(String),
            verification: be_a(String),
            avatar_url: (be_a(String).or be_nil),
            state: be_kind_of(String),
            payin_ability: (be(true).or be(false)),
          )
        end
      end
    end
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接