Forum: RSpec testing a post to a controller's create action

Posted by Patrick Collins (patrick99e99)
on 2011-11-19 03:02
(Received via mailing list)
I just spent a lot of time trying to get a test to pass that would not 
pass no
matter what I did, and I finally decided to just do something really 
simple to
verify that even that was working-- and it's not.

class PostsController < ApplicationController

  def create
    debugger # or binding.pry
  end

end

--

#controllers/post_spec.rb

describe PostsController do

  it "does not work" do
    post :create
  end

end

...

I never see the debugger prompt..  Can anyone PLEASE tell me why this is 
not
working?  In any other test, binding.pry or debugger interrupts the test 
flow
and gives me access to the current scope of the debugger call.

Patrick J. Collins
http://collinatorstudios.com
Posted by David Chelimsky (Guest)
on 2011-11-19 05:14
(Received via mailing list)
On Nov 18, 2011, at 7:42 PM, Patrick J. Collins wrote:

> end
>
> end
>
> ...
>
> I never see the debugger prompt..  Can anyone PLEASE tell me why this is not
> working?  In any other test, binding.pry or debugger interrupts the test flow
> and gives me access to the current scope of the debugger call.

Got any authentication in front of posts#create?
Posted by Mike Mazur (Guest)
on 2011-11-19 05:56
(Received via mailing list)
Hi,

On Sat, Nov 19, 2011 at 09:42, Patrick J. Collins
<patrick@collinatorstudios.com> wrote:
> end
>
> --
>
> #controllers/post_spec.rb

Shouldn't this be called `spec/controllers/posts_controller_spec.rb`?

> I never see the debugger prompt.. Can anyone PLEASE tell me why this is not
> working? In any other test, binding.pry or debugger interrupts the test flow
> and gives me access to the current scope of the debugger call.

Also as David suggests, perhaps it's a before_filter somewhere sending
a response before the request is passed to PostsController#create.

Mike
Posted by Patrick Collins (patrick99e99)
on 2011-11-19 19:48
(Received via mailing list)
> > I never see the debugger prompt..  Can anyone PLEASE tell me why this is not
> > working?  In any other test, binding.pry or debugger interrupts the test flow
> > and gives me access to the current scope of the debugger call.
>
> Got any authentication in front of posts#create?

Yeah, that was the problem!  There was authentication in the
ApplicationController and so I needed a skip_before_filter...

Ok..  Taking this a step forward, what I really am trying to do is write
tests that isolate some methods that are called by before_filters in my
controller.

So, lets say I have something like:

class PostsController < ApplicationController

  before_filter :store_params, :only => :create

  def create
    ... create stuff
  end

  def store_params
    session[:post_params] = params[:post]
  end

end

What I would like to do is totally isolate store_params...  So I'd do 
something
like:

describe "#store_params" do
  it "saves the post params for later" do
    session[:post_params].should be_blank

   post :create, { :post => { :fake_param => "foobar" } }

   session[:post_params][:fake_param].should == "foobar"
  end
end

...  Ok this is all groovy, except my tests will fail because fake_param 
is not
an attribute on Post, and when it actually gets to the create method it
essentially be trying to do Post.create!(:fake_param => "foobar")...  So
obviously I could choose to go about this differently and use a real 
post
attribute-- but I am just curious if there is a way to isolate the 
testing of
this method and make #create not even connected in any way?

I was thinking originally that I should be able to do (prior to the post 
create
call):

PostsController.any_instance.stubs(:create).returns true

But that doesn't seem to do anything...


Patrick J. Collins
http://collinatorstudios.com
Posted by Justin Ko (Guest)
on 2011-11-19 23:12
(Received via mailing list)
On Nov 19, 2011, at 9:57 AM, Patrick J. Collins wrote:

> tests that isolate some methods that are called by before_filters in my
>  end
> describe "#store_params" do
>  it "saves the post params for later" do
>    session[:post_params].should be_blank
>
>    post :create, { :post => { :fake_param => "foobar" } }
>
>    session[:post_params][:fake_param].should == "foobar"
>  end
> end

#store_params is not an "action", therefore I would make it private.

Since it is used only by the #create action, I would spec it as part of 
the #create spec:

describe '#create' do
  it 'stores the :post params' do
    post :create, { :post => { :fake_param => "foobar" } }
    session[:post_params][:fake_param].should == "foobar"
  end
end


>
> ...  Ok this is all groovy, except my tests will fail because fake_param is not
> an attribute on Post, and when it actually gets to the create method it
> essentially be trying to do Post.create!(:fake_param => "foobar")...  So
> obviously I could choose to go about this differently and use a real post
> attribute-- but I am just curious if there is a way to isolate the testing of
> this method and make #create not even connected in any way?
>
> I was thinking originally that I should be able to do (prior to the post create
> call):

You can, but you should mock Post.create!:

describe '#create' do
  it 'stores the :post params' do
    Post.should_receive(:create!).and_return(true)
    post :create, { :post => { :fake_param => "foobar" } }
    session[:post_params][:fake_param].should == "foobar"
  end
end
Posted by Patrick Collins (patrick99e99)
on 2011-11-20 00:06
(Received via mailing list)
> #store_params is not an "action", therefore I would make it private.

I agree completely, and probably should have specified that in my sample 
code
(it certainly is private in my real non-hypothetical implementation).

> Since it is used only by the #create action, I would spec it as part of the 
#create spec:

Well this came up for me because I have a bit of a complex controller 
which has
several before_filters, and I wanted a clean way to really verify that 
each and
every filter is doing what it should, so that's why I wanted to spec 
those
individual methods.

> You can, but you should mock Post.create!:
>
> describe '#create' do
>   it 'stores the :post params' do
>     Post.should_receive(:create!).and_return(true)
>     post :create, { :post => { :fake_param => "foobar" } }
>     session[:post_params][:fake_param].should == "foobar"
>   end
> end

Unfortunately, this project is using a plugin called "Decent Exposure" 
which
does various magic to simplify controller code (and honestly has been a 
big
headache).  So, the create action actually calls post.save!

the 'post' that .save! is called on is actually auto-generated by a 
method
created by Decent Exposure.  It returns a new record pre-populated with
params[:post]...

The failure I see is:

     Failure/Error: post :create, { :post => { :fake_param => "foobar" } 
}
         ActiveRecord::UnknownAttributeError:
        unknown attribute: foo
     # ./app/controllers/posts_controller.rb:107:in `new'


...  So, I can't stub out "create!", because this failure is happening 
before
there...  And, if I do:  Post.stubs(:new).returns(true)

I still get the same failure.

I even took it a step further where I explicitly told decent exposure to 
use a
method:

expose(:post) do
  exposed_for_post if params[:post]
end

def exposed_for_post
  Post.new(params[:post])
end

..  And guess what?  Doing subject.stubs(:exposed_for_post).returns true

still gives me the same failure, unknown attribute: foo in 'new'... 
But,
manually testing stuff proves that exposed_for_post method IS being 
called, and
everything is working..  I just want an automated test to prove it.

I am totally stumped on this one...

Patrick J. Collins
http://collinatorstudios.com
Posted by Justin Ko (Guest)
on 2011-11-20 06:58
(Received via mailing list)
On Nov 19, 2011, at 4:03 PM, Patrick J. Collins wrote:

> individual methods.
>
>     Failure/Error: post :create, { :post => { :fake_param => "foobar" } }
> I even took it a step further where I explicitly told decent exposure to use a
> ..  And guess what?  Doing subject.stubs(:exposed_for_post).returns true
> rspec-users mailing list
> rspec-users@rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users


Please post the backtrace.
Posted by Patrick Collins (patrick99e99)
on 2011-11-20 08:52
(Received via mailing list)
> Please post the backtrace.

Failures:

  1) PostsController#store_post_params stores the last post params in 
the session
     Failure/Error: post :create, { :submit_action => submit_type.to_s, 
:post => { :foo => "bar" } }
     ActiveRecord::UnknownAttributeError:
       unknown attribute: foo
     # ./app/controllers/posts_controller.rb:107:in `new'
     # ./app/controllers/posts_controller.rb:107:in 
`exposed_for_session'
     # ./app/controllers/posts_controller.rb:9
     # ./app/controllers/posts_controller.rb:37:in `create'
     # ./spec/controllers/post_spec.rb:6:in `do_post'
     # ./spec/controllers/post_spec.rb:25

-------------

and exposed_for_session is:

  def exposed_for_session
    Post.new(session.delete(:last_post_params)) if 
session[:last_post_params]
  end

Like I said, I tried stubbing out the new class method on post by doing 
something like:

fake_post = stub('Post', :save => true)
Post.stubs(:new).returns(fake_post)

But I still get that same failure.

Patrick J. Collins
http://collinatorstudios.com
Posted by Justin Ko (Guest)
on 2011-11-20 09:39
(Received via mailing list)
On Nov 19, 2011, at 11:14 PM, Patrick J. Collins wrote:

>     # ./app/controllers/posts_controller.rb:9
>  end
> _______________________________________________
> rspec-users mailing list
> rspec-users@rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users

I haven't used ActiveRecord in quite awhile (been using MongoDB), but it 
looks like you cannot instantiate a record with attributes that don't 
exist. I think you have two options here:

1.) Only use valid attributes in your params.
2.) Add `with` to your stub to exactly match the arguments to `.new`:
      Post.stubs(:new).with({last_post_params: {foo: 
'bar'}}).returns(fake_post)
Posted by Patrick Collins (patrick99e99)
on 2011-11-20 10:01
(Received via mailing list)
> I haven't used ActiveRecord in quite awhile (been using MongoDB), but it looks 
like you cannot instantiate a record with attributes that don't exist. I think you 
have two options here:
>
> 1.) Only use valid attributes in your params.
> 2.) Add `with` to your stub to exactly match the arguments to `.new`:
>       Post.stubs(:new).with({last_post_params: {foo: 'bar'}}).returns(fake_post)

Yeah..  I originally had tried your 2nd option...  Putting an 
expectation on
the arguments sent to new made no difference.  I just tried it again to 
verify,
and I still get the same error.  Seems so weird to me.

Patrick J. Collins
http://collinatorstudios.com
Posted by David Chelimsky (Guest)
on 2011-11-20 13:57
(Received via mailing list)
On Nov 20, 2011, at 2:46 AM, Patrick J. Collins wrote:

>> I haven't used ActiveRecord in quite awhile (been using MongoDB), but it looks 
like you cannot instantiate a record with attributes that don't exist. I think you 
have two options here:
>>
>> 1.) Only use valid attributes in your params.
>> 2.) Add `with` to your stub to exactly match the arguments to `.new`:
>>      Post.stubs(:new).with({last_post_params: {foo: 'bar'}}).returns(fake_post)
>
> Yeah..  I originally had tried your 2nd option...  Putting an expectation on
> the arguments sent to new made no difference.  I just tried it again to verify,
> and I still get the same error.  Seems so weird to me.

Then use the first! The potential problem is that the params change and 
you need to change them here, but you can alleviate that with a method 
like valid_attributes:

describe PostsController do
  def valid_attributes
    { :title => "Isolating change" }
  end

  describe "#store_params" do
    it "saves the post params for later" do
      session[:post_params].should be_blank
      post :create, { :post => valid_attributes }
      session[:post_params].should eq(valid_attributes)
   end
  end
end

You could also use FactoryGirl.attributes_for(:post) if you're using 
that tool. Either way, this removes the need to figure out where to stub 
what on the model. If you're concerned about the DB call, you could 
still stub save! but using valid_attributes would get you past the 
validations that happen before save!.

HTH,
David
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.