Ruby on rails 如何从脚手架上完成rspec put控制器测试

Ruby on rails 如何从脚手架上完成rspec put控制器测试,ruby-on-rails,rspec,factory-bot,rspec-rails,scaffolding,Ruby On Rails,Rspec,Factory Bot,Rspec Rails,Scaffolding,我正在使用脚手架生成rspec控制器测试。默认情况下,它会将测试创建为: let(:valid_attributes) { skip("Add a hash of attributes valid for your model") } describe "PUT update" do describe "with valid params" do let(:new_attributes) { skip("Add a hash of att

我正在使用脚手架生成rspec控制器测试。默认情况下,它会将测试创建为:

  let(:valid_attributes) {
    skip("Add a hash of attributes valid for your model")
  }

  describe "PUT update" do
    describe "with valid params" do
      let(:new_attributes) {
        skip("Add a hash of attributes valid for your model")
      }

      it "updates the requested doctor" do
        company = Company.create! valid_attributes
        put :update, {:id => company.to_param, :company => new_attributes}, valid_session
        company.reload
        skip("Add assertions for updated state")
      end
使用FactoryGirl,我用以下内容填写:

  let(:valid_attributes) { FactoryGirl.build(:company).attributes.symbolize_keys }

  describe "PUT update" do
    describe "with valid params" do
      let(:new_attributes) { FactoryGirl.build(:company, name: 'New Name').attributes.symbolize_keys }

      it "updates the requested company", focus: true do
        company = Company.create! valid_attributes
        put :update, {:id => company.to_param, :company => new_attributes}, valid_session
        company.reload
        expect(assigns(:company).attributes.symbolize_keys[:name]).to eq(new_attributes[:name])
这是可行的,但似乎我应该能够测试所有属性,而不仅仅是测试更改后的名称。我尝试将最后一行更改为:

class Hash
  def delete_mutable_attributes
    self.delete_if { |k, v| %w[id created_at updated_at].member?(k) }
  end
end

  expect(assigns(:company).attributes.delete_mutable_attributes.symbolize_keys).to eq(new_attributes)
这几乎奏效了,但我从rspec得到了以下错误,与BigDecimal字段有关:

   -:latitude => #<BigDecimal:7fe376b430c8,'0.8137713195 830835E2',27(27)>,
   -:longitude => #<BigDecimal:7fe376b43078,'-0.1270954650 1027958E3',27(27)>,
   +:latitude => #<BigDecimal:7fe3767eadb8,'0.8137713195 830835E2',27(27)>,
   +:longitude => #<BigDecimal:7fe3767ead40,'-0.1270954650 1027958E3',27(27)>,
-:纬度=>#,
-:经度=>#,
+:纬度=>#,
+:经度=>#,
使用rspec、factory_girl和脚手架非常普遍,所以我的问题是:

使用有效参数进行PUT更新的rspec和factory_测试的一个好例子是什么?
是否需要使用
属性。符号化_键
并删除可变键?我怎样才能让那些大的十进制对象作为
eq
计算?

好吧,我做了一件非常简单的事情,我使用的是制造商,但我很确定FactoryGirl也是这样:

  let(:new_attributes) ( { "phone" => 87276251 } )

  it "updates the requested patient" do
    patient = Fabricate :patient
    put :update, id: patient.to_param, patient: new_attributes
    patient.reload
    # skip("Add assertions for updated state")
    expect(patient.attributes).to include( { "phone" => 87276251 } )
  end

还有,我不知道你们为什么要建一个新工厂,PUT动词应该是用来添加新东西的,对吗?。如果您首先添加的内容(
new_attributes
)在
放在同一个模型中之后碰巧存在,那么您正在测试的内容。

好的,我就是这样做的,我并不假装严格遵循最佳实践,但我关注测试的精度、代码的清晰性以及套件的快速执行

因此,让我们以一个
UserController

1-我不使用FactoryGirl定义要发布到控制器的属性,因为我希望保持对这些属性的控制。FactoryGirl对于创建记录很有用,但是您应该始终手动设置您正在测试的操作中涉及的数据,这有利于可读性和一致性

在这方面,我们将手动定义发布的属性

let(:valid_update_attributes) { {first_name: 'updated_first_name', last_name: 'updated_last_name'} }
2-然后我定义了更新记录所需的属性,它可以是发布属性的精确副本,但也可以是控制器做了一些额外的工作,我们还想对此进行测试。我们举个例子,一旦我们的用户更新了他的个人信息,我们的控制器就会自动添加一个
need\u admin\u validation
标志

let(:expected_update_attributes) { valid_update_attributes.merge(need_admin_validation: true) }
这也是您可以为必须保持不变的属性添加断言的地方。字段
age
的示例,但它可以是任何内容

let(:expected_update_attributes) { valid_update_attributes.merge(age: 25, need_admin_validation: true) }
3-我在
let
块中定义动作。与前面的2个
let
一起,我发现它使我的规范非常可读。同时,它也使编写共享示例变得容易

let(:action) { patch :update, format: :js, id: record.id, user: valid_update_attributes }
4-(从那时起,所有内容都在我的项目中的共享示例和自定义rspec匹配器中)是创建原始记录的时间了,因此我们可以使用FactoryGirl

let!(:record) { FactoryGirl.create :user, :with_our_custom_traits, age: 25 }
如您所见,我们手动设置
age
的值,以验证它在
update
操作期间没有更改。此外,即使工厂已经将年龄设置为25岁,我也会覆盖它,这样如果我更改工厂,我的测试就不会中断

需要注意的第二件事:这里我们使用
let砰的一声。这是因为有时您可能需要测试控制器的失败操作,最好的方法是存根
valid?
并返回false。一旦存根
有效?
就不能再为同一个类创建记录了,因此
让我们砰的一声将在
有效的存根之前创建记录。

5-断言本身(最后是您问题的答案)

总结因此,添加以上所有内容,这就是规范的外观

describe 'PATCH update' do
  let(:valid_update_attributes) { {first_name: 'updated_first_name', last_name: 'updated_last_name'} }
  let(:expected_update_attributes) { valid_update_attributes.merge(age: 25, need_admin_validation: true) }
  let(:action) { patch :update, format: :js, id: record.id, user: valid_update_attributes }
  let(:record) { FactoryGirl.create :user, :with_our_custom_traits, age: 25 }
  before { action }
  it {
    assert_record_values record.reload, expected_update_attributes
    is_expected.to redirect_to(record)
    expect(controller.notice).to eq('User was successfully updated.')
  }
end

assert\u record\u values
是帮助您简化rspec的工具

def assert_record_values(record, values)
  values.each do |field, value|
    record_value = record.send field
    record_value = record_value.to_s if (record_value.is_a? BigDecimal and value.is_a? String) or (record_value.is_a? Date and value.is_a? String)

    expect(record_value).to eq(value)
  end
end
正如您在使用这个简单的帮助程序时所看到的,当我们需要一个
BigDecimal
时,我们只需编写以下代码,帮助程序就可以完成其余的工作

let(:expected_update_attributes) { {latitude: '0.8137713195'} }

因此,最后,作为总结,当您编写了共享的示例、帮助程序和自定义匹配程序时,您可以让您的规格保持超级干燥。一旦你开始在你的控制器规格中重复同样的事情,你就会发现如何重构它。开始可能需要时间,但完成后,您可以在几分钟内为整个控制器编写测试


最后一句话(我不能停下来,我爱Rspec)是我的全职助手的样子。它实际上适用于任何东西,而不仅仅是模型

def assert_records_values(records, values)
  expect(records.length).to eq(values.count), "Expected <#{values.count}> number of records, got <#{records.count}>\n\nRecords:\n#{records.to_a}"
  records.each_with_index do |record, index|
    assert_record_values record, values[index], index: index
  end
end

def assert_record_values(record, values, index: nil)
  values.each do |field, value|
    record_value = [field].flatten.inject(record) { |object, method| object.try :send, method }
    record_value = record_value.to_s if (record_value.is_a? BigDecimal and value.is_a? String) or (record_value.is_a? Date and value.is_a? String)

    expect_string_or_regexp record_value, value,
                            "#{"(index #{index}) " if index}<#{field}> value expected to be <#{value.inspect}>. Got <#{record_value.inspect}>"
  end
end

def expect_string_or_regexp(value, expected, message = nil)
  if expected.is_a? String
    expect(value).to eq(expected), message
  else
    expect(value).to match(expected), message
  end
end
def assert_records_值(记录,值)
expect(records.length).to eq(values.count),“预期记录数,got\n\n记录:\n#{records.to_a}”
记录。每个带索引的|记录,索引|
断言记录值记录,值[索引],索引:索引
结束
结束
def assert_record_值(记录、值、索引:nil)
值。每个do |字段,值|
record_value=[field].flatte.inject(record){|对象,方法|对象。try:send,方法}
record_value=record_value.to_s if(record_value.is_a?BigDecimal and value.is_a?String)或(record_value.is_a?Date and value.is_a?String)
expect\u string\u或\u regexp记录\u值,值,
“#{”(index#{index})“如果index}值预期为.get”
结束
结束
def expect_string_或_regexp(值,应为,消息=nil)
如果预期的话,你是a吗?一串
预期(值)。到eq(预期),消息
其他的
预期(值)。要匹配(预期),消息
结束
结束

以下是我测试PUT的方法。这是我的
notes\u controller\u spec
中的一个片段,主要思想应该很清楚(如果没有,请告诉我):


我将为(:company)
编写
FactoryGirl.attributes,而不是
FactoryGirl.build(:company).attributes.symbol\u键。它较短,仅包含您在工厂中指定的参数


不幸的是,关于你的问题,我只能说这些了


另外,如果您通过在sty中写入来在数据库层上设置BigDecimal等式检查
def assert_records_values(records, values)
  expect(records.length).to eq(values.count), "Expected <#{values.count}> number of records, got <#{records.count}>\n\nRecords:\n#{records.to_a}"
  records.each_with_index do |record, index|
    assert_record_values record, values[index], index: index
  end
end

def assert_record_values(record, values, index: nil)
  values.each do |field, value|
    record_value = [field].flatten.inject(record) { |object, method| object.try :send, method }
    record_value = record_value.to_s if (record_value.is_a? BigDecimal and value.is_a? String) or (record_value.is_a? Date and value.is_a? String)

    expect_string_or_regexp record_value, value,
                            "#{"(index #{index}) " if index}<#{field}> value expected to be <#{value.inspect}>. Got <#{record_value.inspect}>"
  end
end

def expect_string_or_regexp(value, expected, message = nil)
  if expected.is_a? String
    expect(value).to eq(expected), message
  else
    expect(value).to match(expected), message
  end
end
RSpec.describe NotesController, :type => :controller do
  let(:note) { FactoryGirl.create(:note) }
  let(:valid_note_params) { FactoryGirl.attributes_for(:note) }
  let(:request_params) { {} }

  ...

  describe "PUT 'update'" do
    subject { put 'update', request_params }

    before(:each) { request_params[:id] = note.id }

    context 'with valid note params' do
      before(:each) { request_params[:note] = valid_note_params }

      it 'updates the note in database' do
        expect{ subject }.to change{ Note.where(valid_note_params).count }.by(1)
      end
    end
  end
end
expect{ subject }.to change{ Note.where(valid_note_params).count }.by(1)
it "updates the requested patient" do
  patient = Patient.create! valid_attributes
  patient_before = JSON.parse(patient.to_json).symbolize_keys
  put :update, { :id => patient.to_param, :patient => new_attributes }, valid_session
  patient.reload
  patient_after = JSON.parse(patient.to_json).symbolize_keys
  patient_after.delete(:updated_at)
  patient_after.keys.each do |attribute_name|
    if new_attributes.keys.include? attribute_name
      # expect updated attributes to have changed:
      expect(patient_after[attribute_name]).to eq new_attributes[attribute_name].to_s
    else
      # expect non-updated attributes to not have changed:
      expect(patient_after[attribute_name]).to eq patient_before[attribute_name]
    end
  end
end
class Hash
  def symbolize_and_stringify
    Hash[
      self
      .delete_if { |k, v| %w[id created_at updated_at].member?(k) }
      .map { |k, v| [k.to_sym, v.to_s] }
    ]
  end
end
  let(:valid_attributes) { FactoryGirl.attributes_for(:company) }
  let(:valid_session) { {} }

  describe "PUT update" do
    describe "with valid params" do
      let(:new_attributes) { FactoryGirl.attributes_for(:company, name: 'New Name') }

      it "updates the requested company" do
        company = Company.create! valid_attributes
        put :update, {:id => company.to_param, :company => new_attributes}, valid_session
        company.reload
        expect(assigns(:company).attributes['name']).to match(new_attributes[:name])
      end
let(:valid_attributes){ hash_of_your_attributes} .. like below
let(:valid_attributes) {{ first_name: "Virender", last_name: "Sehwag", gender: "Male"}
  } 
let(:invalid_attributes) {{first_name: "br"}
  }
  validates :first_name, length: {minimum: 5}, allow_blank: true