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
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 know very little about FactoryGirl, and have only ever created single-record factories with no associations. Here are two factories I have now for associated models:
factory :help_request do
name "Mrs. Bourque's holiday party"
description "We need volunteers to help out with Mrs. Bourque's holiday party."
end
factory :donation_item do
name "20 Cookies"
end
Whenever I've needed to associate two records I do it in rspec after the fact with code like this:
require 'spec_helper'
describe HelpRequest do
let(:help_request) { FactoryGirl.create(:help_request) }
let(:donation_item) { FactoryGirl.create(:donation_item) }
subject { help_request }
before {
donation_item.help_request_id = help_request.id
donation_item.save!
}
Ordinarily this has worked, but now I validate that there is at least one donation_item not already marked for destruction:
class HelpRequest < ActiveRecord::Base has_many :events
has_many :donation_items, dependent: :destroy
validate :check_donation_items?
def has_donation_items?
self.donation_items.collect { |i| !i.marked_for_destruction? }.any?
end
def check_donation_items?
if !has_donation_items?
errors.add :a_help_request, "must have at least one item."
end
end
When I run my model test, everything fails with the following:
Failure/Error: let(:help_request) { FactoryGirl.create(:help_request) }
ActiveRecord::RecordInvalid:
Validation failed: A help request must have at least one item.
How can I associate the donation item right in the factory at the time the help_request gets created? I see other answers that seem related, but because my understanding of FactoryGirl is so rudimentary, I can't figure out how to make it work.
factory :donation_item do
name "20 Cookies"
help_request
end
factory :help_request do
name "Mrs. Bourque's holiday party"
description "We need volunteers to help out with Mrs. Bourque's holiday party."
end
Then in your spec:
let(:donation_item) { FactoryGirl.create(:donation_item, help_request: FactoryGirl.create(:help_request)) }
Edit
Do not include help_request assocation in the :donation_item factory, and do this in your test:
let(:help_request) do
help_request = build(:help_request)
help_request.donation_items << build(:donation_item)
help_request.save!
help_request
end
subject { help_request }
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 have the following factory for patient_allergies
FactoryGirl.define do
factory :patient_allergy do
patient
name 'Peanuts'
end
end
The following factory for patient_allergy_reactions
FactoryGirl.define do
factory :patient_allergy_reaction do
patient_allergy
name 'Fever'
severity 'High'
end
end
The model for patient_allergy looks like this:
class PatientAllergy < ActiveRecord::Base
belongs_to :patient
has_many :patient_allergy_reactions
end
the model for patient_allergy_reaction looks like this:
class PatientAllergyReaction < ActiveRecord::Base
belongs_to :patient_allergy
end
My model tests look this:
it 'returns correct allergies with reactions' do
#create an allergy
allergy_name = 'Peanuts'
patient_allergy = create(:patient_allergy, name: allergy_name, patient: patient)
#create a allergy reaction
reaction_name = 'Fever'
reaction_severity = 'Low'
allergy_reaction = create(:patient_allergy_reaction, name: reaction_name, severity: reaction_severity, patient_allergy: patient_allergy)
expect(patient.patient_allergies.size).to eq(1)
expect(patient.patient_allergies[0]).to eq(patient_allergy)
expect(patient.patient_allergies[0].patient_allergy_reactions[0]).to eq(allergy_reaction)
end
The above works fine but doesnt seem to add much value.
I am trying to figure out a way to use build and traits for the above test.
Else, is there a way to use the expect(patient).to have_many(:patient_allergies) matcher or something.
It would be really helpful if i could understand testing my models with factory girl.
The above works fine but doesnt seem to add much value
Agreed. Your model specs should test methods that you write, instead of testing the behavior of Rails.
If you want to test your associations, you can check out shoulda-matchers, which has standard tests for Rails models.
I have an object
class User < ActiveRecord::Base
has_one :subscription
end
and I have this test:
it "should increment shipped count when item_shipped" do
#user.attributes = #valid_attributes
#user.save
subscription = mock_model(Subscription)
subscription.stub!(:item_shipped!)
subscription.stub!(:user_id)
#user.subscription = subscription
lambda{#user.item_shipped!}.should change{#user.shipped_count}.by(1)
end
But I am getting an error:
1)
Spec::Mocks::MockExpectationError in 'User should increment shipped count when item_shipped'
Mock "Subscription_1113" received unexpected message :[]= with ("user_id", 922717357)
./spec/models/user_spec.rb:29:
I am not sure how to mock this out and I can't seem to find any references to this kind of thing.
Instead of mocking Subscription, try stubbing out the methods on an actual Subscription instead:
subscription = Subscription.new
subscription.stub!(:item_shipped!)
subscription.stub!(:user_id)
#user.subscription = subscription
Mocks can be brittle. Any call to a mock must be anticipated and declared as an expectation. It doesn't appear that this particular test needs that model mocked in any case.
EDIT: Also remember to declare any return values that the calling class depends on. In your case this might look like:
subscription.stub!(:item_shipped!).and_return(true)
subscription.stub!(:user_id).and_return(#user.id)
etc.
Again, if you're not asserting that a method on your mocked model should be called, then the only thing mocking does here is make your test brittle. Mocks are meant for things like:
subscription.should_receive(:some_method).once
Otherwise you simply need to stub out methods that have undesirable side effects that don't concern your spec.
Setting up associations for tests is made easier with factories: (untested)
Factory.define :subscriber, :class => User do |f|
f.name "Moe Howard"
f.association :subscription, :factory => :subscription
end
Factory.define :subscription, :class => Subscription do |f|
end
it "should increment shipped count when item_shipped" do
#user = Factory.create(:subscriber)
lambda{#user.item_shipped!}.should change{#user.shipped_count}.by(1)
end
Of course you're not really testing the association here -- you're testing the item_shipped method, which is what you really wanted.
change: mock_model(Subscription) to mock_model(Subscription).as_null_object
which will allow for any messages to be sent to the object (assuming this is an acceptable behavior in your case)