[rspec-users] mocking methods in the controller.

David Chelimsky dchelimsky at gmail.com
Fri Jul 6 12:17:10 EDT 2007


On 7/6/07, Daniel N <has.sox at gmail.com> wrote:
>
>
>
> On 7/6/07, David Chelimsky <dchelimsky at gmail.com> wrote:
> > On 7/6/07, Daniel N <has.sox at gmail.com> wrote:
> > > On 7/6/07, Daniel N <has.sox at gmail.com> wrote:
> > > > On 7/6/07, David Chelimsky < dchelimsky at gmail.com> wrote:
> > > > > On 7/5/07, Daniel N <has.sox at gmail.com> wrote:
> > > > > > Hi,
> > > > > >
> > > > > > I'm very new to rspec so please be patient with me.
> > > > > >
> > > > > > I've tried to take some of my tests out of the controller specs to
> > > check for
> > > > > > things that are rendered.
> > > > > >
> > > > > > This has not worked so well, since my views have the controller
> method
> > > > > >
> > > > > > current_user
> > > > > >
> > > > > > in quite a few places.
> > > > > >
> > > > > > Is there any way that I can define this so that my views will be
> > > executed?
> > > > > > Will this same thing occur for all helper methods that are
> definied in
> > > the
> > > > > > controller?
> > > > >
> > > > > If I understand you correctly, you are trying to take tests for
> views
> > > > > that were previously rails functional tests and turn them into rspec
> > > > > view examples. If that is the case, you should be able to do this:
> > > > >
> > > > >
> > >
> template.stub!(:current_user).and_return(mock_model(User))
> > > > >
> > > > > If not, please provide an explicit example so we can better
> understand
> > > > > what you're talking about.
> > > > >
> > > > > Cheers,
> > > > > David
> > > >
> > > >
> > > >
> > > > Thanx David,
> > > >
> > > > That looks like what I was looking for.
> > > >
> > > > I will try it when I get home.
> > > >
> > > > Cheers
> > > > Daniel
> > >
> > > Ok I've started to have a crack at this but it's getting way out of
> hand.
> > > Everytime I stub a method there's another one to do.
> > >
> > > I've also found that when there's a partial _detail and I've passed the
> > > :collection => @things to it it blows up complaining that the local
> variable
> > > is nil in
> > > dom_id( detail )
> >
> > If you're using the trunk, you can do this:
> >
> > template.expects_render(:partial => 'detail', :collection => @things)
> >
> > >
> > > Am I doing someting wrong?  The start of my specs is
> > >
> > >   before do
> > >     @u = mock_model( User )
> > >     @book = mock_model( Book )
> > >
> > >     public_book = mock_model( Book )
> > >     private_book = mock_model( Book )
> > >     public_book.stub!(:title=).and_return( "Public
> Title" )
> > >     private_book.stub!(:title=).and_return( "Private
> Title"
> > > )
> > >     public_book.stub!( :title ).and_return( "Public Title" )
> > >     private_book.stub!( :title ).and_return( "Private Title" )
> >
> > Why are you stubbing the same calls twice each?
> >
> > >
> > >     @u.stub!( :public_books ).and_return( [public_book] )
> > >     @u.stub!( :private_books ).and_return( [private_book] )
> > >     @clip = mock_model( Clip )
> > >     @clip.stub!( :id ).and_return( 1 )
> > >     @clips = [ @clip ]
> > >
> > >     template.stub!( :current_user ).and_return( @u )
> > >   end
> > >
> > > and I've only started.  Is it normal to have to stub this much for a
> view?
> >
> > Depends on how much stuff is in your view :)
> >
> > You've got a couple of options. You could create instances of the
> > model instead. As long as you're not saving and retrieving them
> > there's very little db interaction - just enough for AR to discover
> > the attributes.
> >
> > If you prefer to keep it all mocked/stubbed, you could clean up a bit like
> this:
> >
> > before do
> >   @u = mock_model( User )
> >   @book = mock_model( Book )
> >
> >   public_book = mock_model(Book, :title => 'Public Title')
> >   private_book = mock_model( Book, :title => 'Private Title')
> >
> >   @u.stub!( :public_books ).and_return( [public_book] )
> >   @u.stub!( :private_books ).and_return( [private_book] )
> >
> >   @clips = [ @clip = mock_model( Clip, :id => 1 ) ]
> >
> >   template.stub!( :current_user ).and_return( @u )
> > end
> >
> > That stubs the same amount of stuff, but its a little cleaner. You
> > could also write a shared behaviour that stubs current user:
> >
> > describe "authenticated page view", :shared => true do
> >   before(:each) do
> >     template.stub!( :current_user ).and_return( @u )
> >   end
> > end
> >
> > describe "/some/page"
> >   it_should_behave_like "authenticated page view"
> >
> >   before(:each) do
> >     @u = mock_model( User )
> >     @book = mock_model( Book )
> >
> >     public_book = mock_model(Book, :title => 'Public Title')
> >     private_book = mock_model( Book, :title => 'Private Title')
> >
> >     @u.stub!( :public_books ).and_return( [public_book] )
> >     @u.stub!( :private_books ).and_return( [private_book] )
> >
> >     @clips = [ @clip = mock_model( Clip, :id => 1 ) ]
> >
> >   end
> > end
> >
> > There's probably a bit more to clean up but I'd have to see the view
> > code. Would you mind letting us see it?
> >
> > >
> > > Cheers
> > > Daniel
>
>
> index.html.erb
>
> <% content_for :action_bar do %>
>   <% @public_books = current_user.public_books %>
>     <% @private_books = current_user.private_books %>
>   <%= render :file => 'books/_book_list.html.erb' -%>
> <% end %>
>
> <% if @book %>
>   <div class='book_clip_list' id='<%= dom_id( @book )-%>'>
>       <h2 class='book_header'><%= @book.title -%></h2>
>         <ul id="book_clip_menu">
>             <li>
>                 <button class='collapse_all'>Collapse All</button>
>                 <button class='expand_all'>Expand All</button>
>             </li>
>         </ul>
>         <%= render :partial => 'detail', :collection => @clips %>
>     </div>
> <% else %>
>   <%= render :partial => 'detail', :collection => @clips -%>
>
> <% end %>
>
>
> detail.html.erb
> <div id='<%= dom_id( detail ) -%>' class='clip_container'>
>     <div class='clip_header'>
>         <button class='minimize'>-</button>
>         <h3 id='<%= "#{dom_id( detail )}_header" -%>'><%= detail.title
> -%></h3>
>         <div>
>             <a href='<%= detail.url -%>' target='_blank'><%=
> URI.parse(detail.url).host -%></a><br/>
>             <%= detail.created_at.to_s ( :long ) -%>
>         </div>
>     </div>
>     <div class='content_container' id='<%= "#{dom_id( detail
> )}_content_container" -%>'>
>     <div class='clip_menu'>
>         <ul class='action_menu'>
>             <%= render :partial => 'action_menu_links', :locals => { :clip
> => detail, :book => @book } -%>
>         </ul>
>         <ul class='view_menu'>
>         <%= render :partial => 'alternative_content_links', :locals => {
> :clip => detail } -%>
>         </ul>
>     </div>
>     <blockquote id='<%= "#{dom_id( detail )}_blockquote"-%>'>
>     <% render_as = @view_type || 'html' %>
>     <% render_as = 'html' unless Clip.available_types.include?( render_as )
> %>
>         <% case render_as %>
>         <% when 'quote' %>
>             <%= render :partial => 'text',     :locals => { :clip => detail
> } %>
>         <% when 'images' %>
>           <%= render :partial => 'images',     :locals => { :clip => detail
> } %>
>         <% when 'html' %>
>             <%= render :partial => 'html',        :locals => { :clip =>
> detail } %>
>         <% end %>
>
>     </blockquote>
>      </div>
> </div>
>
>
> The _text, _images, _html partials are very simple so I won't show them
> here.  This will be enough I think to evaluate it.
>
> Thank you for helping with this.

OK - there's quite a bit going on here, and I don't really have time
to address every detail right now - but here's a couple of things to
think about:

Conventional wisdom suggests that you should favor decision making in
controllers and helpers rather than in the views themselves. One
reason for this is that view templates boil down to a single method,
so you have to provide all of the state they need every time. If your
view has nothing but instance variables that get assigned from the
controller, and never reach into them for their data, then you have
nothing to set up in a test.

Consider this:

<% content_for :action_bar do %>
  <% @public_books = current_user.public_books %>
  <% @private_books = current_user.private_books %>
  <%= render :file => 'books/_book_list.html.erb' -%>
<% end %>

That design choice requires that you set up a current_user which can
provide public_books and private_books. If, instead, you handled that
in the controller, you'd still have to provide a current_user for the
controller examples, but it becomes much easier to start pushing
things around to different methods that can each be tested in
isolation, so the setup for each can get smaller. If you did this, the
view example might look like this:

template.expects_render(:file => 'books/_book_list.html.erb')

instead of this:

template.stub!(:current_user).and_return(
  mock_model(User,
    :public_books => [],
    :private_books => [],
  )
)
template.expects_render(:file => 'books/_book_list.html.erb')

Given what you've got, I think you could do this (with trunk) in before(:each):

============================
controller.stub!(:dom_id).and_return("anything")
template.stub!(:current_user).and_return(
  mock_model(User,
    :public_books => [],
    :private_books => [],
  )
)
template.stubs_render(:file => 'books/_book_list.html.erb')
template.stubs_render(:partial => 'detail', :collection => @clips)

assigns[:clips] => []
assigns[:book] => mock_model(Book)
============================

Then you could use Message Expectations (mocks) in each example for
the specific thing you want to describe.

Hope this helps.

David


>
> Daniel
>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>


More information about the rspec-users mailing list