[rspec-users] render_to_string confusing ControllerSpec

David Chelimsky dchelimsky at gmail.com
Tue Jun 17 07:14:30 EDT 2008


On Mon, Jun 16, 2008 at 10:23 PM, Andrew Selder <aselder at mac.com> wrote:
> David,
>
> Valid point about the method needing some refactoring. But another key rule
> of refactoring is to have passing tests before refactoring.

Catch 22 indeed. Are you familiar w/ Michael Feathers' book Working
Effectively with Legacy Code? The working definition of Legacy Code in
that book is any code without tests. He's got a bunch of strategies in
there for dealing with situations like this - not in Rails, but the
same general concepts and trade-offs apply.

Following that book's advice, you might use integrate_views and create
an example that is more like a Rails functional test to start. Maybe
even an integration test (using RSpec Stories or Rails Integration
Test direction). Then, as you refactor pieces out you can drive that
process w/ smaller examples.

> This is one of the things that's a minor annoyance I have about rails, you
> can't associate views with models. The problem with the refactoring
> suggested is that render_to_string is a member of ActionController::Base,
> and moving it in to a string construction method in the model is complicated
> by no helpers available in the model, and that partial uses tons of helpers.
> So it's hard to move a generate_marker method to the model.

Got it. I wonder if it would be justifiable in a case like this to
create a special object for this purpose then - one that includes all
the helpers  you need. Just a thought. Unusual to do that sort of
thing in Rails, but I used to see and do that sort of thing all the
time in other frameworks. Makes things nicely decoupled and easier to
test.

> Regardless of all of the above, RSpec controller specs have issues when
> testing which template was rendered if you use render_to_string.

I don't think this is an RSpec problem at all. We'd have the same
problem in any other framework. I'd say it's a conflict between Rails
design choices and the desire to test at this level of granularity.
Not saying either is "right." We get a lot of good from both things.
But they are sometimes in conflict.

I'd try testing this at the higher level (as suggested above) to start.

The other thing you might try is mocking render_to_string directly on
the controller.

  controller.should_receive(:render_to_string).with(...).any_number_of_times

Or you could stub do_search and return a known set of props to get
more specific, but to do that you're going to end up with a ton of
mocks and stubs for this example and without anything higher level to
begin with that's not always the safest bet. But all of this is
temporary.

I think we can agree that the goal is trim this puppy down somehow, so
whatever you do to get there with confidence is OK and throwing out
most of that in favor of more granular examples and methods as they
emerge is definitely OK.

Good luck.

Cheers,
David

> Andrew
>
>
>
> On Jun 16, 2008, at 11:05 PM, David Chelimsky wrote:
>
>> On Jun 16, 2008, at 9:19 PM, Andrew Selder wrote:
>>
>>> I updated the spec to look like this:
>>>
>>>  it "should render show template" do
>>>    do_get
>>>    controller.stub_render(:partial => "/properties/map_info_box", :object
>>> => anything())
>>>    response.should render_template('show')
>>>  end
>>>
>>> and I still get the same failure and back trace.
>>>
>>> In addition to not working, the problem with stubbing out that render to
>>> string is that I can't then check for the presence of those strings in the
>>> final output.
>>
>> Well, therein lies the real problem :) This action is exhibiting a number
>> of code smells. I don't know how familiar you might be w/ that, but:
>>
>>  Long Method - a method that does too many things (I count at least 10)
>>  Feature Envy - one one object is doing work on another object's data
>>
>> There's also a lot of asking, violating the Tell Don't Ask principle.
>>
>> This block:
>>
>>  @props.each do |prop|
>>   coords = prop.latlng
>>   unless coords.lat == 0 || coords.lng == 0
>>     prop.info_box = render_to_string(:partial =>
>> "/properties/map_info_box", :object => prop)
>>     mark = prop.has_photos? ? "green" : "red"
>>     marker = GMarker.new(coords, :title => prop_help.bubble_header(prop),\
>>       :info_window => prop.info_box, :icon => Variable.new(mark))
>>     @map.overlay_init marker
>>     @markers << marker
>>   end
>>  end
>>
>> .. could become:
>>
>>  @props.each do |prop|
>>   @markers << prop.generate_marker if prop.needs_marker?
>>  end
>>
>> Much simpler to test at that point! I'd recommend heading down that path.
>>
>> HTH,
>> David
>>
>>
>>
>>> Thanks,
>>>
>>> Andrew
>>>
>>> On Jun 16, 2008, at 10:03 PM, David Chelimsky wrote:
>>>
>>>> On Jun 16, 2008, at 8:59 PM, Andrew Selder wrote:
>>>>
>>>>> Here's the back trace
>>>>>
>>>>> 1)
>>>>> 'SearchesController handling GET /searches/1 should render show
>>>>> template' FAILED
>>>>> expected "show", got "properties/_map_info_box"
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/expectations.rb:52:in
>>>>> `fail_with'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/expectations/handler.rb:25:in
>>>>> `handle_matcher'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/expectations/extensions/object.rb:31:in
>>>>> `should'
>>>>> ./spec/controllers/searches_controller_spec.rb:26:
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/example/example_methods.rb:84:in
>>>>> `instance_eval'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/example/example_methods.rb:84:in
>>>>> `run_with_description_capturing'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/example/example_methods.rb:21:in
>>>>> `execute'
>>>>> /opt/local/lib/ruby/1.8/timeout.rb:48:in `timeout'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/example/example_methods.rb:18:in
>>>>> `execute'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/example/example_group_methods.rb:303:in
>>>>> `execute_examples'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/example/example_group_methods.rb:302:in
>>>>> `each'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/example/example_group_methods.rb:302:in
>>>>> `execute_examples'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/example/example_group_methods.rb:130:in
>>>>> `run'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/runner/example_group_runner.rb:22:in
>>>>> `run'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/runner/example_group_runner.rb:21:in
>>>>> `each'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/runner/example_group_runner.rb:21:in
>>>>> `run'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/runner/options.rb:106:in
>>>>> `run_examples'
>>>>>
>>>>> /Users/aselder/BostonLogic/one_system/vendor/plugins/rspec/lib/spec/runner/command_line.rb:19:in
>>>>> `run'
>>>>> script/spec:4:
>>>>>
>>>>> And here's the controller action:
>>>>>
>>>>> def show
>>>>> @search = Search.find(params[:id])
>>>>> if @search.too_many_results?
>>>>>  message_to_new "Your search returned too many results
>>>>> (#{@search.count}). Please narrow your criteria and search again."
>>>>>  return
>>>>> elsif @search.no_results?
>>>>>  message_to_new "Your search returned no results. Please change your
>>>>> criteria and search again."
>>>>>  return
>>>>> end
>>>>> init_map
>>>>> @props = @search.do_search
>>>>> @markers = []
>>>>> @props.each do |prop|
>>>>>  coords = prop.latlng
>>>>>  unless coords.lat == 0 || coords.lng == 0
>>>>>    prop.info_box = render_to_string(:partial =>
>>>>> "/properties/map_info_box", :object => prop)
>>>>
>>>> This is the line that's causing you trouble. Try stubbing this one out
>>>> as well:
>>>>
>>>> response.stub_render(:partial => "/properties/map_info_box", :object =>
>>>> anything())
>>>>
>>>> Let us know if it works.
>>>>
>>>> Cheers,
>>>> David
>>>>
>>>>>
>>>>>    mark = prop.has_photos? ? "green" : "red"
>>>>>    marker = GMarker.new(coords, :title =>
>>>>> prop_help.bubble_header(prop), :info_window => prop.info_box, :icon =>
>>>>> Variable.new(mark))
>>>>>    @map.overlay_init marker
>>>>>    @markers << marker
>>>>>  end
>>>>> end
>>>>> unless @markers.empty?
>>>>>  @map.center_zoom_on_points_init(*(@markers.collect {|x| x.point}))
>>>>> else
>>>>>  @map.center_zoom_init(@search.center, 12)
>>>>> end
>>>>> end
>>>>>
>>>>> Thanks,
>>>>>
>>>>> Andrew
>>>>>
>>>>> On Jun 16, 2008, at 9:34 PM, David Chelimsky wrote:
>>>>>
>>>>>> On Jun 16, 2008, at 2:58 PM, Andrew Selder wrote:
>>>>>>
>>>>>>> Hi all,
>>>>>>>
>>>>>>> I have a controller I'm trying to spec out, and I'm running into some
>>>>>>> issues with render_to_string.
>>>>>>>
>>>>>>> Basically, the show gets an array of objects, calls render_to_string
>>>>>>> for each of them, and then renders the show template.
>>>>>>>
>>>>>>> So I have the standard spec:
>>>>>>>
>>>>>>>
>>>>>>> it "should render show template" do
>>>>>>> do_get
>>>>>>> response.should render_template('show')
>>>>>>> end
>>>>>>>
>>>>>>>
>>>>>>> but when I run the spec I get the following failure:
>>>>>>>
>>>>>>> 'SearchesController handling GET /searches/1 should render show
>>>>>>> template' FAILED
>>>>>>> expected "show", got "properties/_map_info_box"
>>>>>>>
>>>>>>>
>>>>>>> Does anybody have any ideas?
>>>>>>
>>>>>> Please post the controller action and the full backtrace:
>>>>>>
>>>>>> script/spec spec/controller/path/to/the/spec.rb -fsb
>>>>>>
>>>>>> Thx
>>>>>> _______________________________________________
>>>>>> rspec-users mailing list
>>>>>> rspec-users at rubyforge.org
>>>>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>>>>
>>>>> _______________________________________________
>>>>> rspec-users mailing list
>>>>> rspec-users at rubyforge.org
>>>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>>>
>>>> _______________________________________________
>>>> rspec-users mailing list
>>>> rspec-users at rubyforge.org
>>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>>
>>> _______________________________________________
>>> rspec-users mailing list
>>> rspec-users at rubyforge.org
>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>
>> _______________________________________________
>> rspec-users mailing list
>> rspec-users at rubyforge.org
>> http://rubyforge.org/mailman/listinfo/rspec-users
>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>


More information about the rspec-users mailing list