Rspec Testing Has_Many :Through and After_Save

rspec testing has_many :through and after_save

I've had similar problems in the past that have been resolved by reloading the association (rather than the parent object).

Does it work if you reload thing.followers in the RSpec?

it "should have followers" do
@thing.followers.reload
@thing.followers.should == [@user]
end

EDIT

If (as you mention) you're having problems with the callbacks not getting fired then you could do this reloading in the object itself:

class Thing < ActiveRecord::Base
after_save { followers.reload}
after_save :do_stuff
...
end

or

class Thing < ActiveRecord::Base
...
def do_stuff
followers.reload
...
end
end

I don't know why RSpec has issues with not reloading associations but I've hit the same types of problems myself

Edit 2

Although @dantswain confirmed that the followers.reload helped alleviate some of the problems it still didn't fix all of them.

To do that, the solution needed a fix from @kikuchiyo which required calling save after doing the callbacks in Thing:

describe Thing do
before :each do
...
@user.things << @thing
@thing.run_callbacks(:save)
end
...
end

Final suggestion

I believe this is happening because of the use of << on a has_many_through operation. I don't see that the << should in fact trigger your after_save event at all:

Your current code is this:

describe Thing do
before(:each) do
@user = User.create!(:name => "Fred")
@thing = Thing.create!(:name => "Foo")
@user.things << @thing
end
end

class Thing < ActiveRecord::Base
after_save :do_stuff
...

def do_stuff
followers.each { |f| puts "I'm followed by #{f.name}" }
end
end

and the problem is that the do_stuff is not getting called. I think this is the correct behaviour though.

Let's go through the RSpec:

describe Thing do
before(:each) do
@user = User.create!(:name => "Fred")
# user is created and saved

@thing = Thing.create!(:name => "Foo")
# thing is created and saved

@user.things << @thing
# user_thing_relationship is created and saved
# no call is made to @user.save since nothing is updated on the user
end
end

The problem is that the third step does not actually require the thing object to be resaved - its simply creating an entry in the join table.

If you'd like to make sure that the @user does call save you could probably get the effect you want like this:

describe Thing do
before(:each) do
@thing = Thing.create!(:name => "Foo")
# thing is created and saved

@user = User.create!(:name => "Fred")
# user is created BUT NOT SAVED

@user.things << @thing
# user_thing_relationship is created and saved
# @user.save is also called as part of the addition
end
end

You may also find that the after_save callback is in fact on the wrong object and that you'd prefer to have it on the relationship object instead. Finally, if the callback really does belong on the user and you do need it to fire after creating the relationship you could use touch to update the user when a new relationship is created.

Table count does not increase after save returns true for nested resource rspec test

As I was using Mongoid and 'Mastertags' was embedded into Project, there is no separate collection for Mastertags.

I had to change the code to :

describe "POST create" do

context "with valid params" do
it "creates a new Mastertag" do

expect {
post :create, { project_id: @project.id, mastertag: FactoryGirl.attributes_for(:mastertag_without_project) }
}.to change {@project.reload.mastertags.count}.by(1)
end
end

I got help from this Stackoverflow question : RSpec/Mongoid: Expect to change count on embedded models

Testing before_save with RSpec and Factory Girl

It looks like your record is just showing you old data, and needs to be refreshed. Try:

record.reload.item_id.should == 2

Testing models with relationships and callbacks in Rails with RSpec and Factory_Girl

update_attribute is for updating the attributes of the current object. That means you need to call update_attribute on the object whose attribute you want to update. In this case, you want to update the recipe, not the ingredient. So you have to call recipe.update_attribute(:total_percentage, ...).

Also, ingredients belong to recipes, not other ingredients. So instead of self.ingredients.sum(:percentage) you should really be calling recipe.ingredients.sum(:percentage).

Also, you'll need to reload @recipe before testing it's total_percentage. Even though it refers to the same database record as @ingredient.recipe, it's not pointing to the same Ruby object in memory, so updates to one won't appear in the other. Reload @recipe to fetch the latest values from the database after saving the @ingredient.

has_many :through creating child after_save -- ActionView::Template::Error

Besides being very weird to create a random value in the after_save callback (which I think you're doing as an exercise, but anyway it's better to use good practices from the start), you should never use rand(Model.count) to get a sample record. There's two main problems:

  1. The rand(upper_bound) method returns a number between zero and the upper_bound argument, but there's no guarantee that zero is the first created id. I'm using PostgreSQL and the first model has the id 1. You can specify a range (rand(1..upper_bound)), but anyway you're gambling on the way the current database works.
  2. You're assuming that all the records exist in a sequential order at any given time, which is not always true. If you delete a record and it's id is randomly chosen, you'll get an error. The library also can use any strategy to create the fixtures, so it's better not to assume anything about how it works.

If you really need to choose randomly a record, I'd recommend simply using the array's sample method: Food.all.sample. It's slow, but it works. If you need to optimize, there's other options.

Now, I'd really recommend to avoid random values at all costs, using them only when necessary. It's difficult to test, and difficult to track bugs. Also, I'd avoid creating a relation inside a callback, it grows rapidly into a unmanageable mess.

Rspec controller test for callback after_save

I am assuming that the guest_params method in the controller looks something like this:

def guest_params
params.require(:guest).permit(....)
end

If that is the case, you need to update the POST call in your test case thusly:

post :create, {guest: params}

On a side note, your controller is unnecessarily bloated. I would read up on working with associated models to streamline your code, specifically, using accepts_nested_attributes_for:

http://guides.rubyonrails.org/association_basics.html#detailed-association-reference
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

RSPEC test NAME ERROR - Undefined Local variable or method

You're absolutely right - because you defined the associated post method inside of post_spec.rb, it can't be called from inside vote_spec.rb.

You have a couple options: you can copy your associated post method and put it inside vote_spec.rb, or you can create a spec helper file where you define associated_post once and include it in both vote_spec.rb and post_spec.rb. Hope that helps!



Related Topics



Leave a reply



Submit