I'm using rspec and factory girl to develop an application which has users and each user has many media post. To go with TDD I'm using factory_girl.
After setting up the application, I got two files in spec/factories:
users.rb
FactoryGirl.define do
factory :user do
sequence(:first_name) {|n| "FirstName-#{n}"}
sequence(:last_name) {|n| "LastName-#{n}"}
sequence(:email) {|n| "email-#{n}#example.com"}
password 'chandan123'
password_confirmation 'chandan123'
end
end
And the medias.rb
FactoryGirl.define do
factory :media do
sequence(:description) {|n| "Description #{n}"}
sequence(:url) {|n| "http://www.example-#{n}.com"}
association :user, factory: :user
factory :public_media do
permission Media.permissions[:is_public]
end
factory :private_media do
permission Media.permissions[:is_private]
end
end
end
Now, when I run rspec, I'm getting following error:
$ rspec
Finished in 0.08492 seconds (files took 3.21 seconds to load)
0 examples, 0 failures
/Users/chandankumar/.rvm/gems/ruby-2.1.4/gems/activemodel-4.2.0/lib/active_model/attribute_methods.rb:433:in `method_missing': undefined method `user=' for #<Media:0x007fb5d0b57b50> (NoMethodError)
from /Users/chandankumar/.rvm/gems/ruby-2.1.4/gems/factory_girl-4.4.0/lib/factory_girl/attribute_assigner.rb:16:in `block (2 levels) in object'
from /Users/chandankumar/.rvm/gems/ruby-2.1.4/gems/factory_girl-4.4.0/lib/factory_girl/attribute_assigner.rb:15:in `each'
from /Users/chandankumar/.rvm/gems/ruby-2.1.4/gems/factory_girl-4.4.0/lib/factory_girl/attribute_assigner.rb:15:in `block in object'
from /Users/chandankumar/.rvm/gems/ruby-2.1.4/gems/factory_girl-4.4.0/lib/factory_girl/attribute_assigner.rb:14:in `tap'
from /Users/chandankumar/.rvm/gems/ruby-2.1.4/gems/factory_girl-4.4.0/lib/factory_girl/attribute_assigner.rb:14:in `object'
from /Users/chandankumar/.rvm/gems/ruby-2.1.4/gems/factory_girl-4.4.0/lib/factory_girl/evaluation.rb:12:in `object'
from /Users/chandankumar/.rvm/gems/ruby-2.1.4/gems/factory_girl-4.4.0/lib/factory_girl/strategy/build.rb:9:in `result'
from /Users/chandankumar/.rvm/gems/ruby-2.1.4/gems/factory_girl-4.4.0/lib/factory_girl/factory.rb:42:in `run'
My query is, why its not able to recognise user factory? Why I'm getting above exception.
UPDATE:
I had to add migration to do association at database/model level. Once that's done it worked out perfectly. My bad, I was under impression that rspec will give me failure message instead of exception.
If I were test drive the Media model - I'd rather try to implement proper methods before defining factories. So, I'd start with something like:
spec/models/media_spec.rb
RSpec.describe Media do
subject { Media.new }
# ...
it { is_expected.to respond_to :user }
end
When running your tests, this will drive you to creating proper association in Media:
class Media < ActiveRecord::Base
belongs_to :user
end
After fulfilling this requirement, I would provide sufficient factories based on functionality we have, thus mentioned exception would not be thrown.
Related
This issue is happening in an upgrade that I'm working from Rails 3.2 to Rails 4.2. It might be related to the many things that Rails 4 broke.
I have the following factory:
factory :account do
sequence(:email) {|n| "email#{n}#example.org" }
sequence(:name) {|n| "Name #{n}" }
end
This model has an has_many association called ips. At a specific test, I need to set the account with an ip. In Rails 3.2 I was able to do this:
FactoryGirl.create(:account, :ips => [FactoryGirl.create(:ip)])
But in Rails 4 I get an exception:
ActiveRecord::RecordInvalid: Validation failed: Ips is invalid
I was able to verify that I'm not longer able to override an has_many association. Example:
account = FactoryGirl.build(:account)
account.ips = [FactoryGirl.create(:ip)]
account.save!
It will also throw an exception.
As a side note, using << works fine but that's not what I want since I want to delete any IPs that are assigned to the account and only set the new one.
What is the proper way of doing this in Rails 4?
For this I would recommend using a trait with an after create. So it would look something like this:
FactoryGirl.define do
factory :account do
sequence(:email) {|n| "email#{n}#example.org" }
sequence(:name) {|n| "Name #{n}" }
end
trait :with_ip do
transient do
ip_count 5
end
after(:create) do |account, evaluator|
create_list(:ip, evaluator.ip_count, account: account)
end
end
end
See this for further reference: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md
Edit: This issue is resolved. The issue was caused by a conflict specific to my application. Another module dynamically created a method named .sources. I was able to troubleshoot by removing the relationship and inspecting the objects method list. Thanks anyways.
I'm using Rails 4.1 with Mongoid 4.0 and have setup a relation as follows:
class Organization
include Mongoid::Document
has_many :sources
end
and
class Source
include Mongoid::Document
belongs_to :organization
end
Then in my rspec test I have:
require 'rails_helper'
RSpec.describe PartnershipsController, :type => :controller do
describe "POST #record" do
it "should create a partnership for the source's organization" do
organization = FactoryGirl.create(:organization)
source = FactoryGirl.create(:source)
organization.sources.push source
end
end
There's more after, but the test fails at the organization.sources.push source line with:
undefined method `push' for nil:NilClass
I don't understand why the error is happening. Looks like in the mongoid documentation that's how I should be adding the related source, but so far no dice. What is the correct way to make this relation?
Edit: adding factory
Here's the organization factory, in case it helps clarify something:
FactoryGirl.define do
factory :organization do
app_name = Faker::App.name
company_name = Faker::Company.name
sequence(:name) { |n| "#{([app_name, company_name].sample)}#{n}" }
defaults_hash = { 'item_type' => 'charity', 'child_item_type' => 'product'}
defaults defaults_hash
end
end
I have an example of code not passing in test but working in the console.
Failing Test:
describe ImporterProfile do
it 'sends defaults method to EventAttribute model' do
expect(ListPage).to receive(:new) #passes
expect(EventAttribute).to receive(:new) #fails
ImporterProfile.new.standard_profile
end
1) ImporterProfile standard_profile sends new method to associated objects
Failure/Error: importer_profile.standard_profile
NoMethodError:
undefined method `each' for nil:NilClass
# ./app/models/importer_profile.rb:51:in `standard_profile'
# ./spec/models/importer_profile_spec.rb:29:in `block (3 levels) in <top (required)>'
Models:
class ImporterProfile < ActiveRecord::Base
has_one :list_page, dependent: :delete
has_many :event_attributes, dependent: :delete_all
accepts_nested_attributes_for :list_page
accepts_nested_attributes_for :event_attributes
def standard_profile
self.list_page = ListPage.new
self.event_attributes = EventAttribute.new
end
end
class EventAttribute < ActiveRecord::Base
belongs_to :importer_profile
end
class ListPage < ActiveRecord::Base
belongs_to :importer_profile
end
However, running this method in the console instantiates a new ImporterProfile, ListPage and several EventAttribute objects.
Anyone understand what is going on here?
I suspect that the problem is that you are mocking EventAttribute.new, but only returning nil, so Rails can't enumerate the active records as is required by the self.event_attributes = statement. (It needs to set the foreign key attribute of the EventAttribute records to the id of the ImporterProfile record.)
If you don't mind continuing with execution, you can do:
expect(EventAttribute).to receive(:new).and_call_original
Alternatively, you can return a double, but you'll need to provide stubs for whatever methods ActiveRecord requires, either by using a library such as http://rubygems.org/gems/rspec-active_record_mocks/versions/0.1.4 or by rolling your own.
As an aside, this question would have been a little easier to answer if you'd provided some way to associate the line numbers in the error stack trace with the sources you provided. Also, the comments on your expect statements that the first passes and the second fails is confusing because it appears that you are raising an error before the expectations are being checked.
I wan't to test a has_many association on a class:
class Course < ActiveRecord::Base
has_many :modules
end
For this I wrote a test (Rspec):
describe Course do
it { should have_many(:modules) }
end
For some reason however this test fails:
1) Course should have many modules
Failure/Error: it { should have_many(:modules) }
NoMethodError:
undefined method `column_names' for Module:Class
# ./spec/models/course_spec.rb:4:in `block (2 levels) in <top (required)>'
Does someone has an idea why this test fails? I created a Module class:
class Module > ActiveRecord::Base
belongs_to :course
end
Could it be that 'Module' is a reserved keyword, and therefore I cannot create a class Module?
Thanks for your help,
Anthony
Module is "reserved" name in Ruby (since Ruby has build-in - and very important - Module class). This is probably source of your error.
I have this custom validation method which makes sure you can't vote on your own content, and it causes quite a few RSpec test to fail with undefined method 'user_id' for nil:NilClass
Is there a way to rewrite the custom validation, or use a native rails validation?
failing tests
12) Vote votable type
Failure/Error: #vote = FactoryGirl.create(:vote)
NoMethodError:
undefined method `user_id' for nil:NilClass
# ./app/models/vote.rb:18:in `ensure_not_author'
# ./spec/models/vote_spec.rb:5:in `block (2 levels) in <top (required)>'
validate :ensure_not_author
vote.rb
attr_accessible :value, :votable_id, :votable_type
belongs_to :votable, polymorphic: true
belongs_to :user
def ensure_not_author
votable = self.votable_type.downcase
errors.add(:user_id, "You can't vote on your own content.") if self.votable.user_id == self.user_id
end
if anyone needs more code just shout
it looks like self.votable is nil. It appears as though this test should view the vote from the votable's point of view. If that's the case, then create the vote in the factory for your votable and move the test to your votable entity's suite, as you're really testing that the votable should not be able to vote on its own content.