I have a Rails 5.1 project using rspec/fixtures and I am having trouble getting fixtures to load objects associated with belongs_to/has_one/has_many: the object I requested the fixture for comes back with its _id columns filled with a seemingly-random number and ActiveRecord sees the association as nil
. This occurs on large classes with many associations as well as small data classes with only a few fields.
If, in my test code, I assign those associations with normal Ruby code, objects behave as normal and my tests pass. However when loading the same data through fixtures, associated records are not available and tests that require data spanning across associations fail.
As an example, here are two affected classes:
#app/models/location.rb
class Location < ActiveRecord::Base
has_many :orders
has_many :end_user
belongs_to :retailer
belongs_to :depot
end
#app/models/retailer.rb
class Retailer < ActiveRecord::Base
has_many :locations
end
And here are two corresponding fixtures files:
#spec/fixtures/locations.yml
loc_paris:
retailer: ret_europe (Retailer)
name: "Paris"
nickname: "paris"
loc_washington:
retailer: ret_usa (Retailer)
name: "Washington"
nickname: "washington"
#spec/fixtures/retailers.yml
ret_europe:
name: "AcmeCo France"
nickname: "acmecofr"
currency_type: "EUR"
ret_usa:
name: "AcmeCo USA"
nickname: "acmecousa"
currency_type: "USD"
With the above data, running pp locations(:loc_paris)
results in:
#<Location:0x0000000006eee1d8
id: 35456173,
name: "Paris",
nickname: "paris",
retailer_id: 399879241,
created_at: Wed, 23 May 2018 22:39:56 UTC +00:00,
updated_at: Wed, 23 May 2018 22:39:56 UTC +00:00>
Those id numbers are consistent through multiple calls, at least in the same RSpec context. (I put pp locations(:loc_paris)
in a let
block.) Yet pp locations(:loc_paris).retailer
returns nil
.
I tried using FactoryBot however we had to switch away from it. I am trying to give fixtures an honest shake but it seems like we are best off simply building data objects in the actual test code... because that solutions works without complaining :/
Am I doing something wrong here? Are we asking too much of fixtures?
Thank you!
Tom
Problem with fixtures
Looking at what you've done, locations(:loc_paris)
will find the record described in locations.yml, but locations(:loc_paris).retailer
won't.
Rails Associations work like this:
locations(:loc_paris).retailer
will look for the retailer
with retailer_id
mentioned in locations(:loc_paris)
record. In your case retailer_id: 399879241
and there is no reseller
with this id
that's why it returns Nil
.
Solution: Describe fixtures like this:
#spec/fixtures/locations.yml
loc_paris:
retailer_id: 1
name: "Paris"
nickname: "paris"
loc_washington:
retailer_id: 2
name: "Washington"
nickname: "washington"
#spec/fixtures/retailers.yml
ret_europe:
id: 1
name: "AcmeCo France"
nickname: "acmecofr"
currency_type: "EUR"
ret_usa:
id: 2
name: "AcmeCo USA"
nickname: "acmecousa"
currency_type: "USD"
Now, locations(:loc_paris).retailer
will look for the retailer with retailer_id
mentioned in locations(:loc_paris)
record i.e. retailer_id: 1
and there is a reseller ret_europe
with this id
. Problem Solved
When you run rspec, at first rspec
saves these fixtures into your database with some auto-generated id
values (if id
not provided explicitly), that's why id
and reseller_id
are some random values. If you don't want the id
of locations.yml
record to be some random value, you can provide it yourself like this:
loc_paris:
id: 1
retailer_id: 1
name: "Paris"
nickname: "paris"
Tips:
As rspec
runs in test
environment (mentioned in app/spec/rails_helper.rb) and as I mentioned earlier whenever you run rspec
, at first it saves the fixtures into your database. If your local
and test
database are same, fixtures will replace the actual database records of your database. In your case, records in locations
and resellers
table record will be completely erased and replaced with these fixtures. So, make different database for test
environment.
Hope this answer is helpful