Rails 3.x Test::Unit testing view partials

Recently, for a variety of reasons, I’ve been trying to develop in Rails using, for my testing, only the stock Test::Unit as covered in the Rails Testing Guide. (I can share some of the reasons if anyone’s interested.).

While the Rails Testing Guide is a bit confusing on the subject, it turns out you can use the built-in Rails testing support to test view partials, isolated as view partials, without any reference to a controller.  The Guide only mentions view testing in the context of a controller/action-based testing, and leaves out some details for testing a bare partial.

But it turns out you easily can. If your test class subclasses `ActionView::TestCase` (a class oddly not mentioned in the Testing Guide at all), then it’s got access to a render method that can render partials, as well as the `assert_select` assertions that are mentioned in the guide.

I’ve only figured out the basics, I welcome more information about what’s going on and what you can do!

require 'test_helper'
class WidgetPartialTest < ActionView::TestCase
  def test_widget_partial_render
    render "widget/widget_display", :local1 => "foo"

    assert_select "h2"

It pretty much just works. But note:

  • The `render` that ActionView::TestCase gives you is a render whose signature is similar to the one you have in Rails views, not controllers. The first argument as a string is a partial, not a full view. So the above `render` is looking for a partial in `app/views/widget/_widget_display.something`
    • (I’m not sure if `render` is the code from ordinary rails, or a mocked up testing version which is meant to be like the `render` you call in ActionView, which is subtly different from the render you call in a controller).
  • Similarly, you can pass locals as a direct second hash arg. It doens’t appear you can actually do the ‘:locals => {}’ thing (which I think maybe you can ordinarily use even in view render of partials?)
  • So there’s no way I know of to test a full template rather than a partial. Or is there? Maybe you can pass `:template=>`? Haven’t tried it yet.
    • It probably usually makes sense to test a full template using a controller-based functional test, as the guide suggests. But I won’t say you’d never want to test a full view in isolation — some would suggest you never want to test a partial view in isolation either, but I’m absolutely convinced that it often makes sense to do so (when the partial is re-useable in many places, mainly). But either way, if there’s a way to test a full view in isolation, I haven’t figured it out.
  • There is such a thing in Rails3 as a partial layout too (a layout that can be applied to wrap partials), but I don’t think there’s any way to test a partial layout in isolation using this technique — I’m not sure if you can render a partial with a layout with ActionView::TestCase’s render. Or render a layout by itself with a block for it’s ‘yield’.

Rails: Full vs Partial Views, Really?

This brings up a general point about Rails; I think the distinction between ‘full’ and ‘partial’ views is increasingly ridiculous. You can do pretty much the same things with either one in modern rails. (This wasn’t always the case).

  • Either one can take a layout.
  • Either one takes locals.
  • Either one can be rendered directly from a controller.
  • Not sure if either one can be rendered from inside another view or if  just partials can, but you ought to be able to, IMO.
  • Neither one is actually neccesarily a ‘complete web page’ — even full views typically don’t include <html><head><body> tags, leaving that to a layout.
  • Both can have multiple .format versions, and Rails looks up the right one for current :format when you render.
  • But while you can do much the same things with both full and partial views, there are subtly different ways to do them. And different code paths for doing much the same thing internally in Rails, sometimes.
  • I don’t think there’s any reason for this. They should be interchangeable. The `render` calls in controller and view should be identical, not have subtly different signatures/semantics. (Well, except for automatic-layout being applied from controller `render` and not view render, that’s the only important semantic difference  I can think of).
    • You should be able to render full views from inside other views; it’s perfectly reasonable to have a view which sometimes will be used ‘top level’ within a layout, but other times will be nested in another view. But Rails doesn’t make it easy.
  • It would be awfully nice if someone wanted to take a stab at refactoring partials and views to be identical in Rails4.  Which DHH’s now well known disregard for backwards compatibility, I think it might even make sense for Rails4 to disregard the _underscore convention for naming partials. A view is a view is a view, you ought to be able to call any view from the controller or another view, wrapped in a layout, or not, with `locals` or not, there’s no reason to introduce a distinction.

9 thoughts on “Rails 3.x Test::Unit testing view partials”

  1. Sorry, should have been more clear. The solution to: “But either way, if there’s a way to test a full view in isolation, I haven’t figured it out.”

  2. Huh, cool. I don’t like having to construct the full path on disk in code to do it though. You can really use :file to render a template with full ERB and instance variables and locals, not just a static file?

  3. You don’t have to. I’m calling “ActionController::Base.prepend_view_path [controller view path]” in my setup, works great! (I found this necessary for any partials rendered in the template to be found). And yes, instance variables and passed locals work as expected. I was pretty excited by this, too. :-)

  4. @Daniel, I’m confused. Want to give us a gist? You’re calling `ActionController::Base.prepend_view_path [controller view path]` in your test_support.rb file? In the individual test file? And then despite doing this, you still need to call `render :file` instead of `render :template`? But you use a relative file path? (relative to… something?). I’d love to see a gist example.

    I probably will not update the original article, but let these comments serve as, well, commentary, and additional info. :)

    Although my post was actually about Rails3, come to think of it I expect things will work drastically different in Rails 2.3.x, the relevant plumbing has changed pretty drastically.

  5. I looked at the ActionView::TestCase source code, which led me to think passing :template wouldn’t work. Indeed, it doesn’t. I’m calling it with :file and a template name (not path).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s