Evgeny Bogdanov
2009-Feb-20 16:17 UTC
[rspec-users] How to mock an object defined in the before_filter function?
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
2009-Feb-23 19:32 UTC
[rspec-users] How to mock an object defined in the before_filter function?
On Fri, Feb 20, 2009 at 10:17 AM, Evgeny Bogdanov <evgeny.bogdanov at gmail.com> wrote:> 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.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> ? > > 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 > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Evgeny Bogdanov
2009-Feb-25 09:57 UTC
[rspec-users] How to mock an object defined in the before_filter function?
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
On Feb 23, 8:32?pm, David Chelimsky <dchelim... at gmail.com>
wrote:> On Fri, Feb 20, 2009 at 10:17 AM, Evgeny Bogdanov
>
> <evgeny.bogda... at gmail.com> wrote:
> > 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.
>
> 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
>
>
>
> > ?
>
> > 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
> > _______________________________________________
> > rspec-users mailing list
> > rspec-us... at rubyforge.org
> >http://rubyforge.org/mailman/listinfo/rspec-users
>
> _______________________________________________
> rspec-users mailing list
> rspec-us... at
rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
Pat Maddox
2009-Feb-25 10:42 UTC
[rspec-users] How to mock an object defined in the before_filter function?
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 at gmail.com> wrote:> 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 > > > On Feb 23, 8:32?pm, David Chelimsky <dchelim... at gmail.com> wrote: >> On Fri, Feb 20, 2009 at 10:17 AM, Evgeny Bogdanov >> >> <evgeny.bogda... at gmail.com> wrote: >> > 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. >> >> 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 >> >> >> >> > ? >> >> > 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 >> > _______________________________________________ >> > rspec-users mailing list >> > rspec-us... at rubyforge.org >> >http://rubyforge.org/mailman/listinfo/rspec-users >> >> _______________________________________________ >> rspec-users mailing list >> rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Evgeny Bogdanov
2009-Feb-25 22:40 UTC
[rspec-users] How to mock an object defined in the before_filter function?
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
On Feb 25, 11:42?am, Pat Maddox <pat.mad... at gmail.com>
wrote:> 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.bogda... at gmail.com> wrote:
> > 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
>
> > On Feb 23, 8:32?pm, David Chelimsky <dchelim... at gmail.com>
wrote:
> >> On Fri, Feb 20, 2009 at 10:17 AM, Evgeny Bogdanov
>
> >> <evgeny.bogda... at gmail.com> wrote:
> >> > 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.
>
> >> 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
>
> >> > ?
>
> >> > 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
> >> > _______________________________________________
> >> > rspec-users mailing list
> >> > rspec-us... at rubyforge.org
> >> >http://rubyforge.org/mailman/listinfo/rspec-users
>
> >> _______________________________________________
> >> rspec-users mailing list
> >> rspec-us... at
rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
> > _______________________________________________
> > rspec-users mailing list
> > rspec-us... at rubyforge.org
> >http://rubyforge.org/mailman/listinfo/rspec-users
>
> _______________________________________________
> rspec-users mailing list
> rspec-us... at
rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
Pat Maddox
2009-Feb-26 01:03 UTC
[rspec-users] How to mock an object defined in the before_filter function?
On Wed, Feb 25, 2009 at 2:40 PM, Evgeny Bogdanov <evgeny.bogdanov at gmail.com> wrote:> 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 > ? ? endMy 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
2009-Feb-26 08:34 UTC
[rspec-users] How to mock an object defined in the before_filter function?
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 :) On Feb 26, 2:03?am, Pat Maddox <pat.mad... at gmail.com> wrote:> On Wed, Feb 25, 2009 at 2:40 PM,EvgenyBogdanov > > <evgeny.bogda... at gmail.com> wrote: > > 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 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 > _______________________________________________ > rspec-users mailing list > rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users