Forum: RSpec How to mock an object defined in the before_filter function?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Evgeny Bogdanov (Guest)
on 2009-02-23 17:10
(Received via mailing list)
Hello,

I am trying to implement the following scenario, but I am stuck ...
The thing is I want to initialize a variable @comment when the stub
function "find_comment" is called.
The way I do it below doesn't work, since
"before_filter :find_comment" returns true/false and @comment
initialization is done inside it. Could you please give me a hint how
to do it?

One solution would be to use
Comment.stub!(:find).and_return(@comment) and do not use the stub
find_comment.
But how to do it in general, when there is no expression like "@var =
my_var.function" in the controller and variable @var is defined in
another place and controller just uses it.
my_var.stub!(:function).and_return(@var) doesn't seem to be working.

Is there is something like
my_var.stub!(:function).add_variable(@var).and_return(:true)
?

Thank you,
Evgeny
=============controller_file
  before_filter :find_comment, :only => [:destroy]

  def destroy
    @destroy_id = @comment.id #to be used in rendering partial
    @comment.destroy

    respond_to do |format|
      format.js
    end
  end

  def find_comment
     @comment = Comment.find(:first, :conditions => ['id= ?',params
[:comment_id]])
  end
===============spec_file
spec code
describe CommentsController, "while deleting a comment" do
  it "should render destroy.rjs in case of success" do
    @comment = mock_model(Comment)
    @comment.stub!(:id).and_return(1)
    @comment.stub!(:destroy).and_return(:true)

    controller.stub!(:find_comment).and_return(@comment)

    # execute ajax request
    request.env["HTTP_ACCEPT"] = "application/javascript"
    post :destroy, :comment_id => 1, :item_id => 1, :item_type =>
"Space"

    response.should render_template("destroy")
  end
end
David Chelimsky (Guest)
on 2009-02-23 20:41
(Received via mailing list)
On Fri, Feb 20, 2009 at 10:17 AM, Evgeny Bogdanov
<evgeny.bogdanov@gmail.com> wrote:
> One solution would be to use
> Comment.stub!(:find).and_return(@comment) and do not use the stub
> find_comment.

That's the way to do it.

> But how to do it in general, when there is no expression like "@var =
> my_var.function" in the controller and variable @var is defined in
> another place and controller just uses it.

In general, it's best to avoid dealing directly with internal state on
an object that you're specifying and only manipulate its state through
public methods and/or mocks/stubs on collaborators. So in this case,
I'd just stub Comment.find, as mentioned above.

> my_var.stub!(:function).and_return(@var) doesn't seem to be working.
>
> Is there is something like
> my_var.stub!(:function).add_variable(@var).and_return(:true)

Nope. For the reasons stated above. This is too invasive.

Cheers,
David
Evgeny Bogdanov (Guest)
on 2009-02-25 11:00
(Received via mailing list)
Thank you, David!
One small follow-up question.
Is there is a way to mock an object "@comment" in the controller_spec
for the function?
I didn't manage to do it.

def destroy
    @destroy_id = @comment.id #to be used in rendering partial
    @comment.destroy
end

Looks like assigns[:comment] = mock("comment") works only for views?

Thank you,
Evgeny
Pat Maddox (Guest)
on 2009-02-25 12:00
(Received via mailing list)
Can you show your spec?  I'm not sure why you're using assigns in the
controller spec.  David explained that it doesn't work like that.
Your spec should look something like

it "should destroy the record" do
  mock_comment = stub_model Comment
  Comment.stub!(:find).and_return mock_comment
  mock_comment.should_receive(:destroy)
  delete :destroy, :id => mock_comment.to_param
end

Pat

On Wed, Feb 25, 2009 at 1:57 AM, Evgeny Bogdanov
Evgeny Bogdanov (Guest)
on 2009-02-25 23:46
(Received via mailing list)
The function to specify and specification for it are below.

Comment.stub!(:find).and_return(@comment)
does what I need.
However, it strongly depends on the implementation of find_comment
function,
and if I change it to, let's say, this one:
    def find_comment
       @space = Space.find(1)
       @comment = @space.comment
     end
my spec will fail. Moreover, in my spec I'm checking if the
destroy.rjs is returned on request,
and I don't care about @comment and find_comment. Thus, I would like
just to mock the whole
object @comment for the function "destroy" like it is done with
assigns for view specs, so that my spec doesn't depend on it at all.

Best,
Evgeny
PS. Sorry for bother

@comment = Space.comment, the test will fail.

before_filter :find_comment

     def find_comment
       @comment = Comment.find(:first, :conditions => ['id= ?',params
[:comment_id]])
     end

  def destroy
    @destroy_id = @comment.id #to be used in rendering partial
    @comment.destroy

    respond_to do |format|
      format.js
    end
  end

describe CommentsController, "while deleting a comment" do
  it "should render destroy.rjs in case of success" do

    # stub find_comment call
    @comment = mock_model(Comment)
    @comment.stub!(:id).and_return(1)
    @comment.stub!(:destroy).and_return(:true)

    Comment.stub!(:find).and_return(@comment)

    request.env["HTTP_ACCEPT"] = "application/javascript"
    post :destroy, :comment_id => 1, :item_id => 1, :item_type =>
"Space"

    response.should render_template("destroy")
  end
end
Pat Maddox (Guest)
on 2009-02-26 02:04
(Received via mailing list)
On Wed, Feb 25, 2009 at 2:40 PM, Evgeny Bogdanov
<evgeny.bogdanov@gmail.com> wrote:
>     end
My personal opinion is that the semantics of your app have changed, so
at least some spec somewhere should have to change with it.  There are
a lot of people who don't agree with that.

> my spec will fail. Moreover, in my spec I'm checking if the
> destroy.rjs is returned on request,
> and I don't care about @comment and find_comment. Thus, I would like
> just to mock the whole
> object @comment for the function "destroy" like it is done with
> assigns for view specs, so that my spec doesn't depend on it at all.

I would wrap the stubbing stuff in a method, so that you clearly show
in your test that you don't care about the comment.  e.g.

# spec_helper.rb
def stub_comment
  c = mock_model(Comment)
  Comment.stub!(:find).and_return c
  c
end

describe CommentsController, "DELETE /comments/1" do
  before(:each) do
    stub_comment
  end

  ...
end

Another alternative is to not use a @comment ivar in your controllers
and instead to use a method.  e.g.

def comment
  @comment ||= Comment.find params[:id]
end

and wherever you reference @comment in the controller, ref the method
comment instead.  Then in your spec you can stub it.

You need to inject your mock somehow, but due to the design of Rails
controllers you can't really just "set" something in there, so you
need to either use a factory method which you stub in the controller
spec, or stub out the finder.  If you think that the setup is too
verbose, wrap it in an intention-revealing method.

Pat

Pat
Evgeny Bogdanov (Guest)
on 2009-02-26 09:39
(Received via mailing list)
Thanks a lot Pat for clear and detailed explanations.
My initial vision was that I don't change code and do all the
manipulations in spec files.
Now I understand that it is not really what I should be doing. I
should take into account both,
and adapt one to another to improve clarity and briefness of both
ones.
This might bring me to a better programming style :)
This topic is locked and can not be replied to.