Rspec, testing object receives method - ruby-on-rails

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.

Related

test for models that points itself rspec rails 5

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) }

activerecord shovel operator <<

From inside edit action in the contacts controller have ...
#programs << #contact.program
Which produces the following error:
NoMethodError - undefined method `<<' for Program::ActiveRecord_Relation
Contacts Model:
belongs_to :program
Program Model:
has_many :contacts
validates :name, presence: true, uniqueness: true
#programs.class
Program::ActiveRecord_Relation
#contact.program.class
Program(id: integer, name: string, active: boolean, created_at: datetime, updated_at: datetime)
Question: Why does this operation fail? Why can't the record get added to the record collection. What is preventing the collection(ActiveRecord_Relation) from adding the record?
You're contradicting yourself here:
Program has_many contacts vs Programs << Contact.program
If you're trying to add a Contact to a particular program, you would be looking at adding the contact:
program.contacts << contact
And if you're trying to set the program for the contact:
contact.program = program
What does not make sense, however, is to try to add something to “programs”, which isn't a relationship. Nothing in this system as you've defined it has_many :programs, so #programs.<< cannot possibly act on a relationship.
You're receiving this error because the ActiveRecord::Relation class is only a collection of results returned by an ActiveRecord query. You probably got it by running Program.where or a similar query. It is not an ActiveRecord::Association and therefore you cannot add more records to it.
You must instead use the association returned by the parent object.
Here's an example of what you're doing, vs what you should be doing:
class User < ApplicationRecord
has_many :programs
end
class Program < ApplicationRecord
belongs_to :user
end
new_program = Program.new
# What you're attempting.
programs_where = Program.where(user_id: User.first) # Class is Program::ActiveRecord_Relation
programs_where << new_program # Throws Error b/c << is not available on ActiveRecord::Relation objects.
# What you should be attempting.
user = User.first
programs_assoc = user.programs # Returns Programs::ActiveRecord_Associations_CollectionProxy
programs_assoc << new_program # Returns Correctly
Note: It's not clear how #programs is defined. Is this answer does not work for you then please provide the complete controller code, as well as the other model you're using.

Creating factories for has_many and belongs_to where validation is set on both sides of association

I have the following setup in my codebase. In such a case what would be the correct way to specify the factories so that we get to keep validation on both the models.
Models:
class LabTest < ActiveRecord::Base
has_many :lab_test_fields
validates_presence_of :lab_test_fields
end
class LabTestField < ActiveRecord::Base
belongs_to :lab_test
validates_presence_of :lab_test
end
Factories:
factory :lab_test do
lab_test_fields FactoryGirl.build_list(:lab_test_field, 5)
end
factory :lab_test_field do
lab_test
end
With this setup if I try to create lab_test or lab_test_field factory by doing FactoryGirl.create(:lab_test) or FactoryGirl.create(:lab_test_field), I get Trait not registered: lab_test (ArgumentError) which is quite unexpected.
From what I can see, you have a typo in the factory. This line...
lab_test_fields FactoryGirl.build_list(:lab_test_field, 5)
...is missing an "=" sign.
So it should be:
lab_test_fields = FactoryGirl.build_list(:lab_test_field, 5).
Also, if you're trying to create 5 lab_test_fields, you want to use create_list not build_list. Create will give you a persisted object, while build gives you an object that isn't persisted. Anyway, that missing "=" sign sounds like its why you're getting that error.
Hope this helps!

RSpec with Factory_girl - destroy object

I wan't to test a relationship between two Models.
A course has many enrollments, an enrollment has one course.
When a course is being destroyed, all enrolments connected to it are being set to active = false. This works with real objects, I just can't get the test to work because no matter what I do, the course isn't being destroyed.
describe Enrollment do
it "deactivates enrollment" do
course = create(:a_course)
user = create_user
enrollment = build(:enrollment)
enrollment.course = course
enrollment.user = user
enrollment.save
# until now everything works as expected
expect { course.destroy }.to change { enrollment.active }.to be_false
# the course isn't being destroyed when calling course.destroy
end
end
I couldn't find anything about destroying a factory_girl object in the factory_girl docs, maybe I'm doing it all wrong and I should use "real" objects? Thanks!
Update
Here is the model where the change happens
class Course < ActiveRecord::Base
attr_accessible ...
has_many :users, through: :enrollments
has_many :enrollments
before_destroy :deactivate_enrollments
protected
def deactivate_enrollments
enrollments = self.enrollments
enrollments.each do |e|
e.active = false
e.save
end
end
end
As I'm not really sure about this, the course I'm using to test with is a factory_girl object. It's not created like this: Course.create.... Does the factory_girl object have the same methods as the ActiveRecord object?
Here is the factory_girl code:
FactoryGirl.define do
factory :course, class: Course do
titel "Course title"
end
end
Update 2
Here is the failure message
Enrollment
deactivates enrolment (FAILED - 1)
Failures:
1) Enrollment deactivates enrollment
Failure/Error: expect { course.destroy }.to change(enrollment, :active).from(true).to(false)
active should have been changed to false, but is now true
# ./spec/models/enrollment_spec.rb:18:in `block (2 levels) in <top (required)>'
Update 3
It turns out, the course isn't being destroyed. Neither Course.destroy_all or course.destroy works. No matter if I create the course and enrollment with factory_girl or not. How can this be?
UPDATE
I noticed that you answered your own question by reloading the enrollment but, even so, I think you should change your rspec syntax to be more readable and expressive. The final result could be:
expect {
course.destroy
enrollment.reload
}.to change(enrollment, :active).from(true).to(false)
I think that would be a better way to document the behavior of your code, since it reads almost like fluent english ;)
Thanks for the help! It turns out I have to, after writing course.destroy, write enrollment.reload to be able to see any changes concerning the enrollment.
The test could then look like this:
expect { [course.destroy, enrollment.reload] }.to change { enrollment.active }.to be_false

Rspec Tests fail using custom model validation

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.

Resources