16

我想在控制器测试中使用 FactoryGirl.attributes_for,如:

it "raise error creating a new PremiseGroup for this user" do
  expect {
    post :create, {:premise_group => FactoryGirl.attributes_for(:premise_group)}
  }.to raise_error(CanCan::AccessDenied)
end

...但这不起作用,因为 #attributes_for 省略了 :user_id 属性。#create这是和之间的区别#attributes_for

>> FactoryGirl.create(:premise_group)
=> #<PremiseGroup id: 3, name: "PremiseGroup_4", user_id: 6, is_visible: false, is_open: false)
>> FactoryGirl.attributes_for(:premise_group)
=> {:name=>"PremiseGroup_5", :is_visible=>false, :is_open=>false}

请注意, :user_id 不存在于#attributes_for. 这是预期的行为吗?

FWIW,我的工厂文件包括对:premise_group和的定义:user

FactoryGirl.define do
  ...
  factory :premise_group do
    sequence(:name) {|n| "PremiseGroup_#{n}"}
    user
    is_visible false
    is_open false
  end
  factory :user do
    ...
  end
end
4

5 回答 5

26

简短的回答:

按照设计,FactoryGirlattribues_for故意省略了会触发数据库事务的事情,以便测试能够快速运行。build_attributes但是,如果您愿意花点时间,您可以编写一个方法(如下)来对所有属性进行建模。

原始答案

Digging deep into the FactoryGirl documentation, e.g. this wiki page, you will find mentions that attributes_for ignores associations -- see update below. As a workaround, I've wrapped a helper method around FactoryGirl.build(...).attributes that strips id, created_at, and updated_at:

def build_attributes(*args)
  FactoryGirl.build(*args).attributes.delete_if do |k, v| 
    ["id", "created_at", "updated_at"].member?(k)
  end
end

So now:

>> build_attributes(:premise_group)
=> {"name"=>"PremiseGroup_21", "user_id"=>29, "is_visible"=>false, "is_open"=>false}

... which is exactly what's expected.

update

Having absorbed the comments from the creators of FactoryGirl, I understand why attributes_for ignores associations: referencing an association generates a call to the db which can greatly slow down tests in some cases. But if you need associations, the build_attributes approach shown above should work.

于 2012-04-24T08:26:44.540 回答
1

I think this is a slight improvement over fearless_fool's answer, although it depends on your desired result.

Easiest to explain with an example. Say you have lat and long attributes in your model. On your form, you don't have lat and long fields, but rather lat degree, lat minute, lat second, etc. These later can converted to the decimal lat long form.

Say your factory is like so:

factory :something
  lat_d 12
  lat_m 32
  ..
  long_d 23
  long_m 23.2
end

fearless's build_attributes would return { lat: nil, long: nil}. While the build_attributes below will return { lat_d: 12, lat_m: 32..., lat: nil...}

def build_attributes
  ba = FactoryGirl.build(*args).attributes.delete_if do |k, v| 
    ["id", "created_at", "updated_at"].member?(k)
  end
  af = FactoryGirl.attributes_for(*args)
  ba.symbolize_keys.merge(af)
end
于 2012-12-21T05:40:01.160 回答
0

To further elaborate on the given build_attributes solution, I modified it to only add the accessible associations:

def build_attributes(*args)
    obj = FactoryGirl.build(*args)
    associations = obj.class.reflect_on_all_associations(:belongs_to).map { |a| "#{a.name}_id" }
    accessible = obj.class.accessible_attributes

    accessible_associations = obj.attributes.delete_if do |k, v| 
        !associations.member?(k) or !accessible.include?(k)
    end

    FactoryGirl.attributes_for(*args).merge(accessible_associations.symbolize_keys)
end
于 2013-09-15T15:04:34.730 回答
0

Here is another way:

FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys

Limitations:

  • It does not generate attributes for HMT and HABTM associations (as these associations are stored in a join table, not an actual attribute).
  • Association strategy in the factory must be create, as in association :user, strategy: :create. This strategy can make your factory very slow if you don't use it wisely.
于 2017-07-03T20:32:18.610 回答
0

The accepted answer seems outdated as it did not work for me, after digging through the web & especially this Github issue, I present you:

A clean version for the most basic functionality for Rails 5+

This creates :belongs_to associations and adds their id (and type if :polymorphic) to the attributes. It also includes the code through FactoryBot::Syntax::Methods instead of an own module limited to controllers.

spec/support/factory_bot_macros.rb

module FactoryBot::Syntax::Methods
  def nested_attributes_for(*args)
    attributes = attributes_for(*args)
    klass = args.first.to_s.camelize.constantize

    klass.reflect_on_all_associations(:belongs_to).each do |r|
      association = FactoryBot.create(r.class_name.underscore)
      attributes["#{r.name}_id"] = association.id
      attributes["#{r.name}_type"] = association.class.name if r.options[:polymorphic]
    end

    attributes
  end
end

this is an adapted version of jamesst20 on the github issue - kudos to him

于 2019-07-09T07:42:32.027 回答