How to mock an object defined in the before_filter function?


#1

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


#2

On Fri, Feb 20, 2009 at 10:17 AM, Evgeny Bogdanov
removed_email_address@domain.invalid 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


#3

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


#4

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


#5

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


#6

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 :slight_smile:


#7

On Wed, Feb 25, 2009 at 2:40 PM, Evgeny Bogdanov
removed_email_address@domain.invalid 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