Factory Bot HABTM nil value in join table - ruby-on-rails

I am getting a (frustrating!) error when running rspec (with Factory Bot) tests for my Rails app. I'm seeing the following error:
ActiveRecord::NotNullViolation:
Mysql2::Error: Field 'group_id' doesn't have a default value: INSERT INTO `groups_users` VALUES ()
It is intermittent - different tests will throw the error on different runs - which might suggest a race condition?
My models are configured as:
User habtm Groups
Groups habtm Users
Staff members MUST belong to one group - with a validation of:
validates :groups, presence: { message: 'Staff must be in at least one group' }, if: :staff?
Users
factory :a_staff_member, class: User do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
username { "#{first_name}_#{last_name}".downcase.gsub(/[^a-z]/i, '') }
email { "#{first_name}.#{last_name}#example.ac.uk".downcase }
user_type { :staff }
groups {|s| [s.association(:group)]}
end
Groups
FactoryBot.define do
factory :group do
sequence(:title) { |n| "Group Number #{n}" }
sequence(:short_title) { |n| "GRP#{n}" }
sequence(:url_slug) { |n| "grp#{n}" }
end
end
The common factor in all of the failing tests is the User creation factory (and the Groups creation given a User MUST belong to a group) - so this is where I have been focussing my efforts.
Given that the error is on the groups_users table I'm guessing that it's trying to save an entry to the join table before the group exists in the groups table?
Based on other SO posts and trawling the Internet, I've tried various ways of setting the group:
1) groups {|s| [s.association(:group)]}
2) after(:build) {|user| user.groups = [create(:group)]}
3) groups {[FactoryBot.create(:group)]}
The users are just instantiated via
let!(:staff_user) { FactoryBot.create(:a_staff_member) }
in the individual specs - so nothing exciting there.
However, no mater how I create the groups in the factories it doesn't make any difference - and I've run out of ideas - any pointers or suggestions would be gratefully received!

I think trying to change the equality with concatenation might work.
I'd add after(:create) { |user| user.groups << create(:group) }
Moving to after(:create) is the main thing here as the problem you are facing also seems to be trying to add a join table record for a record that is not created yet.

Managed to track down the issue. After putting some comprehensive debug messages around the point in the factory that the group was created and assigned to the user, it became clear that the error message relating to nil values in the join table between these was, in fact, a red herring and being caused outside of the factory.
I believe the issue was caused by the user session/log in not being cleaned up between tests from Capybara.
Adding:
config.after :each do
Warden.test_reset!
end
to /spec/spec_helper.rb appears to have resolved the issue. Hopefully the hair pulled out during this investigation will grow back!

I think that it might work if you change like this.
from
after(:build) {|user| user.groups = [create(:group)]}
to
after(:create) {|user| user.groups = [create(:group)]}
Once a user id is created by running "create", then "users.groups" will be acceptable.

Related

Build in-test associations using Factory_girl

I want to be able to create associations with ID only in each specific test, trying to avoid defining them in the factory.
I've been following Rails 4 Test Prescriptions
Avoid defining associations automatically in factory_girl definitions.
Set them test by test, as needed. You’ll wind up with more manageable
test data.
class Workspace < ActiveRecord::Base
has_many :projects
end
class Project < ActiveRecord::Base
belongs_to :workspace
end
This is what I want
test "" do
project_with_id = build_stubbed(:project)
workspace_with_id = build_stubbed(:workspace)
workspace_with_id.projects.push(project_with_id)
end
I am using build_stubbed to create valid ID's, which gives the following error:
*** RuntimeError Exception: stubbed models are not allowed to access the database - Project#save({:validate=>true})
So, reading factory girl's documentation I came up with working associations, but I don't want to define them in the factory, not even with traits.
FactoryGirl.define do
factory :project do
association :workspace, strategy: :build_stubbed
end
end
test "" do
project = build_stubbed(:project)
end
This works because I can call project.workspace, and both have a valid ID
How can I create valid associations (with ID), but without touching the database, only using Factory girl to create independent objects?
You could do something like this if you are using Rspec
let!(:user1) { FactoryGirl.create(:user) }
let!(:user_social_profile1) { FactoryGirl.create(:user_social_profile, user_id: user1.id) }
also in Rspec
let!(:user1) { FactoryGirl.create(:user) }
let!(:user_social_profile1) { FactoryGirl.create(:user_social_profile, user: user1) }
In minitest/test_unit I believe
user1 = FactoryGirl.create(:user)
user_social_profile1 = FactoryGirl.create(:user_social_profile, user_id: user1.id)
I am sorry I did not explain the issues associated with using build_stubbed with factory associations, this answer does a really good job at explaining that.
build_stubbed initializes a record and fakes persistence. So what you get is a record that answers true to persisted? and has an faked ID.
Its quite useful but the approach does not work when it comes associations as you would need to stub out large parts of ActiveRecord.
Instead you want to use create:
before do
#project = create(:project)
#workshop = create(:workshop, project: #project)
end
As far as I understand, you don't need an inverse to work, e.g. you'd be satisfied with workspace.projects being empty, wouldn't you?
This should work for you then:
workspace = build_stubbed(:workspace)
project = build_stubbed(:project, workspace: workspace)
If you need workspace.projects, you may use this:
project = build_stubbed(:project)
workspace = build_stubbed(:workspace, projects: [project])
project.workspace = workspace

How do I set up my fixtures for a has_and_belongs_to_many relation?

I have the following models:
class Company < ActiveRecord::Base
has_and_belongs_to_many :regions
class Region < ActiveRecord::Base
has_many :requests
has_and_belongs_to_many :companies
class RequestForProposals < ActiveRecord::Base
belongs_to :region
Whenever I get a new request, I want to send a notification to the companies active in the same region.
How do I set this up in my fixtures so that I can unit test the logic of finding the right companies?
I've tried
region_ids: 1, 2
regions: one, two
in companies.yml, but neither works in assigning regions to the companies.
Here is a gist of the SQL generated: https://gist.github.com/2713518
For
regions: one, two
in companies.yml to work you need to let rails auto assign the ids for the regions. This is because (in order to avoid having to have read regions.yml before companies.yml) rails calculates the ids it sticks in the join table from the names of the companies fixtures. if you've assigned the ids yourself, they won't match up with the calculated ones.
From the sql you've provided it looks like you're setting the ids on the regions to 1 and 2, i.e. your regions.yml has
one:
id: 1
name: MyString
Remove the id:1 and you should be ok. You'll also need to update any other files (e.g. request_for_proposals.yml) that refer to regions, replacing
region_id: 1
with
region: one
Rails will know that means to set region_id to the id for the region with label one in your fixtures.
Is your regions.yml in test/fixtures directory? How about companies.yml? Your code "regions: one, two", for a given company, should work.
Rails automatically loads the fixtures in this directory when you run the test.
If the locations are correct, please post the test output - sql generated - when you run the tests.
In addition to accepted answer if you have a need to calculate such id by yourself, e.g. you have some loose references or another datasource just add:
def fixh(key)
ActiveRecord::FixtureSet.identify key
end
to your test_helper.rb and then use this way in fixtures:
security_context1:
ext_id: <%= fixh :user1 %>
I'm unsure how does one use YAML fixtures these days. Have you tried FactoryGirl for creating db object instances while testing? It does pretty much the same as fixtures, in a much sophisticated way though.
The following passages assumes you are using rspec as a testing framework.
After including factory_girl-rails in your Gemfile and updating spec/spec_helper.rb accordingly to the factory_girls README, create the following files:
# spec/factories/company_factories.rb
FactoryGirl.define do
factory :company do
title { |n| "Test Company #{n}" }
# whatever else fields
after_create { |company| company.regions << FactoryGirl.create(:region) }
end
end
# spec/factories/region_factories.rb
FactoryGirl.define do
factory :region do
# whatever fields here
end
end
# spec/factories/request_factories.rb
FactoryGirl.define do
factory :request do
# whatever fields here
end
end
Now the real question is - what's the actual test code you're doing?

Getting two users from seeds.rb file when I only want one

If this is the only line I have creating a user in my seeds.rb file:
jim = Factory :user, :name => "Jim Jones", :email => 'jim#test.com'
How come I end up with two users, one named Jim, and one with a random name as if it was just generated by the factory?
Thanks!
John
UPDATE: The reason, I realize now, is because another factory is being created which has an association to user, and I don't specify which user, and so it would seem the factory with an association to user is generating a user.
I'm assuming you defined Factory :user somewhere? That's the general use case for Factory anyways. With that defined calling Factory :user will return whatever it was defined to do before. Have a look at the Getting Started With Factory_Girl page.
You should be able to call FactoryGirl.create(:user) from there.
Good luck!

Testing creation of associated objects on callback with Rspec on Rails

Trying to wrap my head around rspec and proper testing and having some hard time to do the following properly
Let's say we have three classes like
Class User
belongs_to :company
has_and_belongs_to_many :roles
end
Class Company
has_many :users
end
Class Role
has_and_belongs_to_many :users
end
In the User class I have before_create callback that assign the user default 'company_admin' role, if the user is first one to be associated with the company
def check_for_initial_company_admin_role
if self.company.users.count == 0
self.roles << Role.find_by_name("company_admin")
end
end
How do I properly test in my model spec that the user gets assigned the 'company_admin' role in case he's the first user associated with the company?
UPDATE
working solution
describe "#check_for_initial_company_admin_role" do
it "sets the first user created to be the administrator" do
Factory(:role)
user = Factory(:user)
user.roles.count.should be > 0
user.roles.should include Role.find_by_name("company_admin")
end
end
Factory.define :user do |f|
f.email { Factory.next(:email) }
f.password "secret"
f.password_confirmation "secret"
f.association :company
end
Factory.define :role do |f|
f.name "company_admin"
end
Factory.define :company do |f|
f.name "foobar"
f.vat_id "1234"
end
I would approach it like this:
describe "#check_for_initial_company_admin_role" do
it "sets the first user created to be the administrator" do
company = Factory(:company)
user = Factory(:user)
company.users << user
user.roles.count.should > 0
user.roles.should include Role.find_by_name("company_admin")
end
end
An assumption here that may be incorrect is that you are using Factory Girl in your test framework. If not, that doesn't really change the "meat" of this test...just those first lines where you create the company and user.
You could also optionally check the user from the company side of things but honestly that feels like a different test entirely--one testing the association between those models.
The approach I would take is that since this is actually a model test you need to create and alter a real model objects, rather than mocking out those objects. If this were a controller test, I'd mock the models and stub the models aggressively.
I hope this helps and addresses your question. If not, let me know where I'm off base and I'll make another pass at it :) I'm only about a year into rspec but I've found that once I wrapped my head around how to test models vs. controllers I've come to love it.
Without changing your existing logic, I would test this logic in the user_spec like so:
describe User do
let!(:admin_role) { Role.create!(name: 'company_admin') }
let!(:company) { Company.create! }
it 'should be added to the default role when created' do
user = company.users.create!(name: 'Joe', email: 'joe#email.com')
user.should have(1).roles
user.roles.first.should == admin_role
end
end
Note: I would be using something like FactoryGirl for the admin role and company objects to make them reusable.
Your use of the role name to indicate behavior is not ideal. It will likely lead to lots of scattered logic throughout your application where you are finding the role by its name and checking the name with if/else or case statements. I would recommend using Rail's single table inheritance and moving all the admin role logic to a separate class. It will keep the model's logic cleaner and make testing much easier.

How can I define multiple associated objects using Factory Girl?

The Factory Girl docs offer this syntax for creating (I guess) parent-child associations...
Factory.define :post do |p|
p.author {|a| a.association(:user) }
end
A post belongs to a User (its "author").
What if you want to define a Factory to create Users, that have a bunch of Posts?
Or what if it's a many-to-many situation (see update below for example)?
UPDATE
I thought I had figured it out. I tried this...
Factory.define(:user) do |f|
f.username { Factory.next(:username) }
# ...
f.roles { |user|
[
Factory(:role),
Factory(:role, {:name => 'EDIT_STAFF_DATA'})
]
}
end
It seemed to work at first, but then I got validation errors because F.G. was trying to save the user twice with same username and email.
So I return to my original question. If you have a many-to-many relationship, like say Users and Roles, how can you define a Factory that will return Users with some associated Roles? Note that Roles must be unique, so I can't have F.G. creating a new "ADMIN" Role in the DB every time it creates a User.
I'm not sure if this is the most correct way of doing it, but it works.
Factory.define(:user) do |u|
u.login 'my_login'
u.password 'test'
u.password_confirmation 'test'
u.roles {|user| [user.association(:admin_role),
user.association(:owner_role, :authorizable_type => 'User', :authorizable_id => u.id) ]}
end
A recent update to factory girl allows associations to be specified with callback blocks
I created an active_factory plugin, that deals with your situation in spec like this:
models { user - posts(3) }
If there is an interest, i may try to create integration with factory_girl factories.

Resources