如何从脚手架完成rspec put控制器测试 [英] How to complete the rspec put controller test from scaffold

查看:81
本文介绍了如何从脚手架完成rspec put控制器测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using scaffolding to generate rspec controller tests. By default, it creates the test as:

  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,我已经在其中填写了内容:

Using FactoryGirl, I've filled this in with:

  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])

这有效,但是似乎我应该能够测试所有属性,而不仅仅是测试更改后的名称.我尝试将最后一行更改为:

This works, but it seems like I should be able to test all attributes, instead of just testing the changed name. I tried changing the last line to:

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字段有关的以下错误:

That almost worked, but I'm getting the following error from rspec having to do with BigDecimal fields:

   -: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和脚手架非常普遍,所以我的问题是:

Using rspec, factory_girl, and scaffolding is incredibly common, so my questions are:

使用有效参数对PUT更新进行rspec和factory_girl测试的一个好例子是什么? 是否有必要使用attributes.symbolize_keys并删除可变键?我怎样才能得到那些BigDecimal对象来评估为eq?

What is a good example of an rspec and factory_girl test for a PUT update with valid params? Is it necessary to use attributes.symbolize_keys and to delete the mutable keys? How can I get those BigDecimal objects to evaluate as eq?

推荐答案

好的,这就是我的工作方式,我不假装严格遵循最佳实践,但我专注于测试的准确性和代码的清晰度,并快速执行我的套件.

Ok so this is how I do, I don't pretend to strictly follow the best practices, but I focus on precision of my tests, clarity of my code, and fast execution of my suite.

所以以UserController

1-我不使用FactoryGirl定义要发布到控制器的属性,因为我想控制这些属性. FactoryGirl对于创建记录很有用,但是您始终应该手动设置要测试的操作中涉及的数据,这样可以更好地提高可读性和一致性.

1- I do not use FactoryGirl to define the attributes to post to my controller, because I want to keep control of those attributes. FactoryGirl is useful to create record, but you always should set manually the data involved in the operation you are testing, it's better for readability and consistency.

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

In this regard we will manually define the posted attributes

let(:valid_update_attributes) { {first_name: 'updated_first_name', last_name: 'updated_last_name'} }

2-然后,我定义期望用于更新记录的属性,它可以是已发布属性的精确副本,但也可以是控制器做了一些额外的工作,我们还希望测试一下.因此,以我们的示例为例,一旦用户更新了他的个人信息,我们的控制器就会自动添加need_admin_validation标志

2- Then I define the attributes I expect for the updated record, it can be an exact copy of the posted attributes, but it can be that the controller do some extra work and we also want to test that. So let's say for our example that once our user updated his personal information our controller automatically add a need_admin_validation flag

let(:expected_update_attributes) { valid_update_attributes.merge(need_admin_validation: true) }

这也是您可以为必须保持不变的属性添加断言的地方.字段为age的示例,但可以是任何内容

That's also where you can add assertion for attribute that must remain unchanged. Example with the field age, but it can be anything

let(:expected_update_attributes) { valid_update_attributes.merge(age: 25, need_admin_validation: true) }

3-我在let块中定义操作.与前2个let一起使用时,我发现它使我的规范更具可读性.而且还可以轻松编写shared_examples

3- I define the action, in a let block. Together with the previous 2 let I find it makes my specs very readable. And it also make easy to write shared_examples

let(:action) { patch :update, format: :js, id: record.id, user: valid_update_attributes }

4-(从那时起,所有内容都在我的项目中的共享示例和自定义rspec匹配器中)现在是时候创建原始记录了,为此我们可以使用FactoryGirl

4- (from that point everything is in shared example and custom rspec matchers in my projects) Time to create the original record, for that we can use FactoryGirl

let!(:record) { FactoryGirl.create :user, :with_our_custom_traits, age: 25 }

如您所见,我们要手动设置age的值,因为我们想验证它在update操作期间没有变化.另外,即使工厂已经将年龄设置为25,我也总是将其覆盖,因此如果我更换工厂,测试不会中断.

As you can see we manually set the value for age as we want to verify it did not change during the update action. Also, even if the factory already set the age to 25 I always overwrite it so my test won't break if I change the factory.

第二件事要注意:这里我们使用let!并加上一声巨响.这是因为有时您可能想测试控制器的失败操作,而执行此操作的最佳方法是对valid?进行存根并返回false.一旦对valid?进行存根,就无法再为同一类创建记录,因此let!带有爆炸声将在valid?

Second thing to note: here we use let! with a bang. That is because sometimes you may want to test your controller's fail action, and the best way to do that is to stub valid? and return false. Once you stub valid? you can't create records for the same class anymore, therefor let! with a bang would create the record before the stub of valid?

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

5- The assertions itself (and finally the answer to your question)

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.')
}

总结因此,添加以上所有内容后,规范便会变成这样

Summarize So adding all the above, this is how the spec looks like

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_record_values是帮助您简化rspec的助手.


assert_record_values is the helper that will make your rspec simpler.

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时可以通过这个简单的帮助器看到的那样,我们可以编写以下内容,然后由该帮助器完成其余的工作

As you can see with this simple helper when we expect for a BigDecimal, we can just write the following, and the helper do the rest

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


因此,最后,并总结一下,当您编写了shared_examples,helper和自定义匹配器后,您可以将规格保持DRY超级.一旦您开始在控制器规格中重复同样的事情,就会发现如何重构它.一开始可能会花费一些时间,但完成后,您可以在几分钟内为整个控制器编写测试


So at the end, and to conclude, when you have written your shared_examples, helpers, and custom matchers, you can keep your specs super DRY. As soon as you start repeating the same thing in your controllers specs find how you can refactor this. It may take time at first, but when its done you can write the tests for a whole controller in few minutes

最后一句话(我无法停止,我爱Rspec)是我的完整助手的样子.实际上,它可用于任何事物,而不仅仅是模型.

And a last word (I can't stop, I love Rspec) here is how my full helper look like. It is usable for anything in fact, not just models.

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 put控制器测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆