RSpec View testing: Mocking helpers vs. mocking controller methods
Wolfram Arnold — Tue, 05/12/2009 - 01:35
RSpec::Rails (I'm using version 1.2.2 as of this writing) has a really cool feature to test view templates. This is one of the features where RSpec really has a leg up on standard Rails functional tests.
You can test the view templates without the controller actions running, which has the nice side-effect that your actions and your views become less tightly coupled—and you're doing BDD, right?
Here is an example of testing a layout, you can do this without even relating to an action or a view template:
describe 'layouts/application' do
before(:each) do
@admin = users(:uber)
template.controller.stub!(:current_user).and_return(@admin)
# Note: this wouldn't work, see below: template.stub!(:logged_in?).and_return(true)
@admin.admin?.should be_true
end
it 'should show admin logo with link to admin_users' do
render 'layouts/application'
response.should have_tag 'div#headerContainer' do
with_tag 'div#logo' do
with_tag 'img', :count => 1
with_tag "a[href=#{admin_users_path}]" do
with_tag "img[src*=header_logo_admin.gif]"
end
end
end
end
end
Consider that my layout has code such as this in it:
<div id="headerContainer">
<div id="header">
<div id="logo">
<% if logged_in? %>
<% if admin? %>
<%= link_to image_tag('header_logo_admin.gif'), admin_users_path %>
<% else -%>
...
<% end -%>
<% else -%>
...
<% end -%>
</div>
</div>
</div>
Both the logged_in? and admin? methods are implemented in application.rb where they call current_user.
The point of my post today is to highlight the difference between:
template.controller.stub!(:current_user).and_return(@admin)
and
template.stub!(:logged_in?).and_return(true)
The latter is what the RSpec::Rails docs on view testing tell us to do. The catch is that this ONLY stubs out helpers. If that helper happens to call a controller method, as is the case here, then the current_user is no longer mocked and the test fails obscurely. In order to make the mock live on for calls to the controller code, you need to mock template.controller. This mock also works in the template directly, so I'm tempted to conclude the former version is more general and works to mock both helpers and controller methods. The price you pay is that your BDD is then no longer encouraging the helper/controller separation as strictly.
See also this post: https://rspec.lighthouseapp.com/projects/5645/tickets/541-error-stubbing...


