Forum: RSpec [RSpec] [Rails] [BDD]

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.
14a9bbd74b4e7fcbcb31e512b45cb150?d=identicon&s=25 julian mann (Guest)
on 2009-02-28 21:01
(Received via mailing list)
Hello,

My first time here, and I'm pretty new to rspec, and indeed BDD/TDD, so
bear
with me

I am trying to spec a fairly simple controller update action for an
account.
The account belongs_to :user and user has_one :account.

 the route is PUT: /users/1/account

here's the action

 def update
    @account = @user.account
    respond_to do |format|
      if @account.update_attributes(params[:account])
        flash[:notice] = ' Account was successfully updated.'
        format.html { redirect_to(user_account_path( @user)) }
      else
        format.html { render :action => "edit" }
      end
    end
  end

I'm trying to avoid fixtures, and I'm using rspec's own mock framework

In normal operation the @user instance variable is assigned in a
before_filter (get_user) - so

i figured if I stub that I could then assign user:

before(:each) do
controller.stub!(:get_user).and_return(true)
instance_variable_set('@user', mock_model(User))
end

I tried other variations on this - like instance_variable_set('@user',
User.new)  and others

but @user is never assigned

 -- (actually I did have that bit working when I started this mail, but
I
messed it up somehow) .. anyway -  then the problem I couldn't get past
after that - and the reason I started writing this mail was: ....

how do I stub out that user's call to :account  and get it to return an
account, so that update_attributes is called? --

( when I was using mocha.. which I abandoned for other reasons - there
was
"any_instance_of" but I can't find similar in rspecs own mocking - and
it
felt a bit wishy-washy anyway.)

It seems there is plenty of help around on speccing RESTful controllers,
but
not when resources are nested

Any input on this will be much appreciated, and really improve my
understanding of mocking. So thanks in advance!


Julian
C694a032be7518a0d704318895f8fe1d?d=identicon&s=25 Ben Mabey (mabes)
on 2009-02-28 21:28
(Received via mailing list)
julian mann wrote:
> Hello,
>
> My first time here, and I'm pretty new to rspec, and indeed BDD/TDD,
> so bear with me

Welcome Julian!  See my comments inline below...
>     respond_to do |format|
>
> I tried other variations on this - like instance_variable_set('@user',
> User.new)  and others
>
> but @user is never assigned

When you use instance_variable_set you are setting the variable inside
the example group (the test) and not the actual controller it self.
In order to use your mock user in your controller you need to stub
whatever your get_user method uses to find the user.  You do not want to
stub out the get_user method otherwise your controller will never be
able to set the user object. For example, say you have this:

def get_user
  @user = User.find(session[:user])
end

In order to stub this correctly you would not stub get_user but instead
stub the find call on User, like so:

before(:each) do
  User.stub!(:find).and_return( @user = mock_model(User))
end

Doing this will stub the user in the controller and also allow you to
access the same object (as @user) in your example group. ( The fact that
I named it @user in the example has nothing to do with it- that is just
to be consistent.)

On another note, what many rails apps do is instead of using filters to
set instance variables you can extract it into an accessor method that
caches it.  For example, instead of having the get_user method as a
filter you could simply have this:

def current_user
  @current_user ||= User.find(session[:user])
end

Then in your action:

def update
    @account = current_user.account
    ....
end

To stub this you could do what I listed above or stub out the
current_user method like so:

before(:each) do
  controller.stub!(:current_user).and_return( @current_user =
mock_model(User))
end


In general you should not stub or mock methods on the object you are
trying to test.  That said, stubbing current_user on the controller is
something that is done often and is usually a low-risk thing to do.  (I
just thought I would point out the bad smell.)


Now, to stub out the account on user, in your examples you would just
need to say something like:

@current_user.stub!(:account).and_return(mock_model(Account))

Or create the account stub inline with the user mock creation:

mock_model(User, :account => mock_model(Account))

HTH,

Ben
14a9bbd74b4e7fcbcb31e512b45cb150?d=identicon&s=25 julian mann (Guest)
on 2009-03-01 01:22
(Received via mailing list)
> Welcome Julian!  See my comments inline below...


Thanks Ben!

>>    respond_to do ...
>> controller.stub!(:get_user).and_return(true)
> example group (the test) and not the actual controller it self.
Ah ok - got it now.  Thanks for clearing that up !
Although it does leave me slightly confused as to what the object is
that
performs the test - i.e. who that instance variable belongs to (not that
I
need it now). I mean - unlike Test::Unit, there is no 'class AccountTest
' -
I'll dig around and find out.

In order to use your mock user in your controller you need to stub
whatever
>
> before(:each) do
>  User.stub!(:find).and_return( @user = mock_model(User))
> end
>

Excellent - I will do this - makes sense now ;)


>
> Doing this will stub the user in the controller and also allow you to
> access the same object (as @user) in your example group. ( The fact that I
> named it @user in the example has nothing to do with it- that is just to be
> consistent.)
>

I see.. so returning ( @user = mock_model(User) ) means the
example_group
can access the user as @user. but returning only ( mock_model(User))
would
mean @user is still set up in the controller, but not available the
example
group.

On another note, what many rails apps do is instead of using filters to
set
> instance variables you can extract it into an accessor method that caches
> it.  For example, instead of having the get_user method as a filter you
> could simply have this:
>
> def current_user
>  @current_user ||= User.find(session[:user])
> end


>
> Then in your action:
>
> def update
>   @account = current_user.account
>   ....
> end


Cool -I'll do this! its a minor adjustment to what I have at the moment.

>
> In general you should not stub or mock methods on the object you are trying
> to test.

That said, stubbing current_user on the controller is something that is
done
> often and is usually a low-risk thing to do.  (I just thought I would point
> out the bad smell.)


Yes I could smell it too --  It seems obvious, in hindsight, that if you
stub out methods on the object you are testing, then you are not really
testing it. So thanks for bringing that up.

> HTH,
>
> Yes it absolutely does help!  Things are really falling into place nicely
now. Thankyou!


Julian
C694a032be7518a0d704318895f8fe1d?d=identicon&s=25 Ben Mabey (mabes)
on 2009-03-01 03:38
(Received via mailing list)
>     When you use instance_variable_set you are setting the variable
>     inside the example group (the test) and not the actual controller
>     it self.
>
>
> Ah ok - got it now.  Thanks for clearing that up !
> Although it does leave me slightly confused as to what the object is
> that performs the test - i.e. who that instance variable belongs to
> (not that I need it now). I mean - unlike Test::Unit, there is no
> 'class AccountTest ' - I'll dig around and find out.

RSpec's DSL creates these classes and objects for you.  You can think of
'describe' as an alias to 'class' in a way.  (It is of course more
complicated than that.)

-Ben
This topic is locked and can not be replied to.