Render_to_string confusing ControllerSpec

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?

Thanks,

Andrew

On Jun 16, 2008, at 2:58 PM, Andrew S. wrote:

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

Here’s the back trace

‘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:inhandle_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:ininstance_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:inexecute’
/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:inexecute’
/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:ineach’
/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:inrun’
/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:ineach’
/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:inrun_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)
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 8:59 PM, Andrew S. wrote:

/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/

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

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.

Thanks,

Andrew

On Jun 16, 2008, at 9:19 PM, Andrew S. wrote:

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 :slight_smile: 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

David,

Valid point about the method needing some refactoring. But another key
rule of refactoring is to have passing tests before refactoring.

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.

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

Andrew

On Mon, Jun 16, 2008 at 10:23 PM, Andrew S. [email protected] 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