简体   繁体   中英

Best way to set up nested associations with RSpec / FactoryGirl

I am trying to build a test suite for a Rails app, and I'm not sure how to build objects with FactoryGirl (I can move to a different tool if necessary).

The tricky part is that there are have deep nested associations, lets take for example this part of the schema : http://imgur.com/HAUS7kr

Let's say I want to do some tests on the "ItemTracker" model. Now there are 2 approaches :

  • In order to have a valid object, I will need to build a Connexion, which needs a Token, which needs a Project, etc... Also, I want the ProjectItem's project to be the same as the Token's project. Same thing with Company (on top). I wouldn't want to have 2 different projects or companies created. So I need a way for these objects to share some associations. One drawback of this approach is that I'm not sure how to make not created several associated objects, and I'm also gonna build a ton of objects I don't necessarily need (except for validations)

  • I could say "for this particular test, the associations are irrelevant, so I'm just gonna create one ItemTracker, without its Connexion or ProjectItem, and do my things with this isolated object". In a general manner, I would just create the objects and associations I actually need. By playing with traits, I found a way to avoid creating the associations if I don't need them. The drawback is that this object will not be valid (there are validations on associations), so every time I'm gonna try to save something on it, I'm gonna have an Exception, which will be a pain in some test cases.

So I guess my question is : what is the best way to set up some objects for testing in this kind of complex object structure ?

Thanks for the feedback ;)

You need to build this whole relation tree anyway, but you can put this into a trait and, in specs, create item tracker with trait with_all_relations , for example. It would look like this:

FactoryGirl.define do
  factory :item_tracker do
    ...
    trait :with_all_relations do
      after(:build) do |item_tracker|
        company = create(:company)
        item = create(:item, company: company)
        user = create(:user, company: company)
        ...
        item_tracker.project_item = project_item
        item_tracker.connexion = connexion
      end
    end
  end
end

And if you specify no trait, you'll get item tracker without any related models.

I want the ProjectItem's project to be the same as the Token's project. Same thing with Company (on top). I wouldn't want to have 2 different projects or companies created

You can use a "named" factory by overriding initialize_with :

factory :project do
  # define common attributes for projects

  factory :project_1 do
    initialize_with do
      Project.find_or_create_by(name: "Project 1")
    end
  end
end

Now you can add through an association:

association :project, factory: project_1

and get the same project in associations on different models.

Note that this smells a lot like a fixture, so you might consider if fixtures would work better than factories for your situation.

I would just create the objects and associations I actually need. By playing with traits, I found a way to avoid creating the associations if I don't need them. The drawback is that this object will not be valid

I recommend creating a 'base' version of each factory that just allows you to set attributes, without associations, even if that is invalid. Then extend that to create valid (and potentially invalid) versions by adding associations. Jeiwan's answer is an good example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM