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.
Related
I'm new doing unit test in Ruby on Rails, so I need some help in a test.
This is my model User.rb
class User < ApplicationRecord
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
end
I want to create a test to verify this association. I tried doing this on my user_spec.rb
describe 'should validates associations' do
subject { User.new }
it { should belong_to(subject.created_by) }
end
This is the error response
Failures:
1) User should validates associations should belong to
Failure/Error: it { should belong_to(subject.created_by) }
Expected User to have a belongs_to association called (no association > called )
# ./spec/models/user_spec.rb:17:in `block (3 levels) in '
The ActiveRecord shoulda matchers do not require any object of the class to be instantiated for the tests to run. Here, you have initialised a new User instance as the subject, and have tried to pass that to the shoulda matcher to check the belongs_to association.
However, in order to check a belongs_to association on a model with a particular foreign key and class name, the following test can be used:
it { should belong_to(:created_by).with_foreign_key(:created_by_id).class_name('User') }
ActiveRecord matchers take lots of other options apart from the two mentioned above. These options are very well documented in the Shoulda code on GitHub
You give an instance to matcher but it waits for the reference name and the referenced class name. Your test should be like following.
it { should belong_to(:created_by).of_type(User) }
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.
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've got a Rails 4 beta app (on Ruby 2) and I'm getting an error I can't make sense out of.
I've got some specs that are failing because my model class has no method 'create', even though I'm inheriting from ActiveRecord::Base. The error message is calling my class a module (undefined method 'create' for Topic:Module), and that seems odd.
spec/models/topic_spec.rb:
require "spec_helper"
describe Topic do
it "should create a new topic given valid attributes" do
Topic.create!({:created_by_id => 1, :title => "Test" })
end
end
app/models/topic.rb
class Topic < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
validates :title => :presence => ture
validates :created_by_id => :presence => true
end
Error message:
$ rspec spec/models/topic_spec.rb
F
Failures:
1) Topic should create a new topic given valid attributes
Failure/Error: Topic.create!({:created_by_id => 1, :title => "Test" })
NoMethodError:
undefined method `create' for Topic:Module
# ./spec/models/topic_spec.rrc:15:in `block (2 levels) in <top (required)>'
It sounds like you have a module or namespace also named Topic that is getting loaded first and so in your tests, Topic is not referring to your class. Are there any other files that have Topic in them, even something like class Topic::Question or similar? If so, try taking them out or being explicit about it. For example, changing:
class Topic::Question < ActiveRecord::Base
to
class Topic
class Question < ActiveRecord::Base