Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is 'FactoryGirl.lint' giving InvalidFactoryError?

Long time reader but first time poster here on SO :)

For the last couple of days I've been setting up FactoryGirl.

Yesterday I changed some factories (mainly my User and Brand factories) by replacing:

Language.find_or_create_by_code('en')

With:

Language.find_by_code('en') || create(:language)

Because the first option creates a Language object with only the code attribute filled in; while the second uses the Language factory to create the object (and thus fills in all the attributes specified in the factory)

Now when I run my test it immediately fails on Factory.lint, stating my user (and admin_user) factories are invalid. Reverting the above code doesn't fix this and the stack trace provided by FactoryGirl.lint is pretty useless..

When I comment the lint function, my tests actually run fine without any issues.
When I manually create the factory in rails console and use .valid? on it, it returns true so I'm at a loss why lint considers my factories invalid.

My user factory looks like this:

FactoryGirl.define do

  factory :user do
    ignore do
      lang { Language.find_by_code('en') || create(:language) }
    end

    login "test_user"
    email "[email protected]"
    name "Jan"
    password "test1234"
    password_confirmation "test1234"
    role               # belongs_to :role
    brand              # belongs_to :brand
    person             # belongs_to :person
    language { lang }  # belongs_to :language

    factory :admin_user do
      association :role, factory: :admin
    end
  end
end

Here the role, person and language factories are pretty straightforward (just some strings) but the brand factory shares the same language as the user thus I use the code in the ignore block so FactoryGirl doesn't create 2 'en' language entries in my database.

Anyone has some ideas why I'm getting this InvalidFactoryError and maybe provide some insights on how to debug this?


UPDATE 1

It seems this problem is caused by another factory..
I have a factory called user_var_widget where I link a specific widget with a user:

  factory :user_solar_widget, :class => 'UserWidget' do
    sequence_number 2
    user { User.find_by_login('test_user') } # || create(:user) }
    widget { Widget.find_by_type('SolarWidget') || create(:solar_widget) }
  end

If I uncomment the create(:user) part, I get InvalidFactoryError for the User factory. My guess is because there is nothing in the User factory that states it has any user_widgets. I will experiment a bit with callbacks to see if I can resolve this.


UPDATE 2

I've managed to solve this by adding this to my User factory:

trait :with_widgets do
  after(:create) do |user|
    user.user_widgets << create(:user_solar_widget, user: user)
  end
end

Where user_widgets is a has_many association in the user model.
Then I changed my user_solar_widget factory to:

factory :user_solar_widget, :class => 'UserWidget' do
  sequence_number 2
  # removed the user line
  widget { Widget.find_by_type('SolarWidget') || create(:solar_widget) }
end

I then create a user by calling:

create :user, :with_widgets  

Still, it would have been nice if the lint function was a bit more specific about invalid factories..

like image 888
Liiva Avatar asked Dec 06 '22 02:12

Liiva


1 Answers

FactoryGirl.lint is almost useless because of it's non-existent error messages. I recommend instead including the following test:

# Based on https://github.com/thoughtbot/factory_girl/
#          wiki/Testing-all-Factories-(with-RSpec)
require 'rails_helper'

RSpec.describe FactoryGirl do
  described_class.factories.map(&:name).each do |factory_name|
    describe "#{factory_name} factory" do
      it 'is valid' do
        factory = described_class.build(factory_name)
        expect(factory)
          .to be_valid, -> { factory.errors.full_messages.join("\n") }
      end
    end
  end
end
like image 126
Dan Kohn Avatar answered Dec 17 '22 17:12

Dan Kohn