#541 open
linoj

error stubbing helpers in application.rb

Reported by linoj | September 26th, 2008 @ 03:34 AM | in No-Milestone-Assigned

Say two helpers, foo and bar, are defined in application.rb, and made available to view using helper_method(). Say foo calls bar. You cannot stub template.stub!(:bar) as foo still calls the original. You can get further by doing template.controller.stub!(:bar) but that seems to wipe out any stubs in template. Attached is a bare minimum demo of the problem (rails 2.0.2)

$ script/spec -v RSpec-1.1.4 (build 20080526202855) - BDD for Ruby

$ script/spec -c spec/views/things/edit.html.erb_spec.rb ......F.F

1) '/things/edit.html.erb should have mighty STUBBED' FAILED expected /mighty STUBBED/, got "

Hello

\n\n

Normal helper

\n\n

stubbed

\n\n

mighty COOL

\n" ./spec/views/things/edit.html.erb_spec.rb:44: script/spec:4:

2) ActionView::TemplateError in '/things/edit.html.erb should stub Normal helper when controller stubbed' undefined method normal_helper' for #<Spec::Rails::Example::ViewExampleGroupController:0x351fa10> On line [#3](/projects/5645/tickets/3 "Ticket #3") of things/edit.html.erb

1: <h1>Hello</h1>
2:
3: <p><%= normal_helper %></p>
4:
5: <p><%= cool %></p>
6:

vendor/plugins/rspec/lib/spec/mocks/proxy.rb:75:in `send'
vendor/plugins/rspec/lib/spec/mocks/proxy.rb:75:in `message_received'
vendor/plugins/rspec/lib/spec/mocks/proxy.rb:105:in `normal_helper'
app/views/things/edit.html.erb:3:in `_run_erb_47app47views47things47edit46html46erb'
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_view/base.rb:637:in `send'
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_view/base.rb:637:in `compile_and_render_template'
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_view/base.rb:365:in `render_template'
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_view/base.rb:316:in `render_file'
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:1100:in `render_for_file'
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:861:in `render_with_no_layout'
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/layout.rb:270:in `render_without_benchmark'
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/benchmarking.rb:51:in `render'
/opt/local/lib/ruby/1.8/benchmark.rb:293:in `measure'
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.0.2/lib/action_controller/benchmarking.rb:51:in `render'
vendor/plugins/rspec-rails/lib/spec/rails/example/view_example_group.rb:130:in `send'
vendor/plugins/rspec-rails/lib/spec/rails/example/view_example_group.rb:130:in `render'
spec/views/things/edit.html.erb_spec.rb:5:in `do_it'
spec/views/things/edit.html.erb_spec.rb:58
vendor/plugins/rspec/lib/spec/example/example_methods.rb:84:in `instance_eval'
vendor/plugins/rspec/lib/spec/example/example_methods.rb:84:in `run_with_description_capturing'
vendor/plugins/rspec/lib/spec/example/example_methods.rb:21:in `execute'
/opt/local/lib/ruby/1.8/timeout.rb:48:in `timeout'
vendor/plugins/rspec/lib/spec/example/example_methods.rb:18:in `execute'
vendor/plugins/rspec/lib/spec/example/example_group_methods.rb:303:in `execute_examples'
vendor/plugins/rspec/lib/spec/example/example_group_methods.rb:302:in `each'
vendor/plugins/rspec/lib/spec/example/example_group_methods.rb:302:in `execute_examples'
vendor/plugins/rspec/lib/spec/example/example_group_methods.rb:130:in `run'
vendor/plugins/rspec/lib/spec/runner/example_group_runner.rb:22:in `run'
vendor/plugins/rspec/lib/spec/runner/example_group_runner.rb:21:in `each'
vendor/plugins/rspec/lib/spec/runner/example_group_runner.rb:21:in `run'
vendor/plugins/rspec/lib/spec/runner/options.rb:106:in `run_examples'
vendor/plugins/rspec/lib/spec/runner/command_line.rb:19:in `run'
script/spec:4

Finished in 0.168537 seconds

9 examples, 2 failures

Comments and changes to this ticket

  • linoj

    linoj September 26th, 2008 @ 04:11 AM

    i've updated the ticket with an even simpler example

  • David Chelimsky

    David Chelimsky September 26th, 2008 @ 02:44 PM

    • → State changed from “new” to “open”

    Hey linoj,

    Thanks for the failing example, but I can't really run it w/o a rails app :)

    Any chance you could patch in a failing example to the rspec-rails code examples?

  • linoj

    linoj September 26th, 2008 @ 05:20 PM

    i'm going away for the weekend so wont have time now but i've attached the files in a rails app

  • David Chelimsky

    David Chelimsky September 29th, 2008 @ 02:54 PM

    • → Tag changed from “mocks rspec_on_rails” to “bug mocks rails rspec_on_rails”

    Hey linoj,

    There are three examples in which you posed questions. I can answer the first 2 but haven't figured out the 3rd.

    
      it "should have mighty STUBBED" do
        # WHY DOESNT THIS WORK
        template.stub!(:cool).and_return("stubbed")
        do_it
        response.should have_text(/mighty STUBBED/)
      end
    
      it "should have might STUBBED using controller" do
        # INTERESTING, THIS DOES WORK
        template.controller.stub!(:cool).and_return("stubbed")
        do_it
        response.should have_text(/mighty STUBBED/)
      end
    

    The reason the 1st fails and the 2nd passes is that these methods, while exposed to the view, are actually delegated to and executed in the controller. So when the view calls mighty_cool, it calls mighty_cool on the controller, which then calls cool, also in the controller (not back in the view).

    
      it "should stub Normal helper when controller stubbed" do
        # WHY DOESNT THIS WORK
        template.controller.stub!(:cool).and_return("stubbed")
        template.should_receive(:normal_helper).and_return('Stubby helper')
        do_it
      end
    

    This one I have no idea about, so I'll leave this open until it's resolved.

  • David Chelimsky

    David Chelimsky September 29th, 2008 @ 04:26 PM

    OK - I've found the bug. It's most complicated.

    When using partial mocks, i.e. adding mock behaviour to real objects, the object is given an instance variable named @mock_proxy, which keeps track of everything related to message expectations and stubs on that object.

    When the example says template.should_receive(:normal_helper).and_return('Stubby helper'), @mock_proxy is assigned on the template, the real #normal_helper method is renamed to #proxied_by_rspec__normal_helper, and a new #normal_helper is defined which interacts with @mock_proxy.

    In this example, both the controller and the template have a @mock_proxy. When rails copies instance variables from the controller to the view, it copies the controller's @mock_proxy over to the view, overwriting view's @mock_proxy. So when the view receives #normal_helper, which is the rspec implementation, it tries to interact with the @mock_proxy, but that mock_proxy has references to the controller, not the view, so when it asks questions about the #normal_helper method, it raises an error, since the controller doesn't know about it.

    EEEEEK.

    My opinion is that this is a rails bug - that if there is already an instance variable with a given name in the view, that the controller should not overwrite it with it's instance variable.

    I'm going to start down that path.

    In the mean time, I'll look into monkey patching rails from rspec to see if we can bandage this in the short term. Until this is resolved one way or the other, however, you simply can't set messsage expectations or stubs on both the controller and the template in the same example.

Please Login or create a free account to add a new comment.

You can update this ticket by sending an email to from your email client. (help)

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

Behaviour Driven Development for Ruby.

Shared Ticket Bins

People watching this ticket

Attachments