Close and Go BackBack to Viget

Ruby on Rails

Helper Testing Independence

Brian Landau
Brian Landau, Web Developer, July 07, 2008 0

Testing helpers is a topic we’ve covered a couple times already here on the Viget Extend blog. I’ve additionally submitted a patch to Rails for adding some helper assertions to ActionView and other helper related testing goodness. But until that gets accepted and committed, I thought I’d hold everyone over with a helper testing plugin.

Independently Testing Helpers

The main tenet here is that helper functionality should be tested outside of another context (i.e. the controller). To achieve that, we need to create helper tests that live independent of our other tests. As Justin pointed out, Rails actually provides a class to assist in this, ActionView::TestCase.

The plugin I’ve created has two generators that help in creating these tests. The first, helper_tests, can be used to generate tests for your existing helpers. It creates a test file for each helper in the test/helpers directory and a test method for each public helper method. You can also pass it a list of helpers you wish to generate tests for and it will skip those that you don’t name.

Usage:

> script/generate helper_tests [SampleHelper Admin::AnotherHelper ...]

The other generator, helper, creates a helper -- and a helper test for the name provided. It will also accept a list of methods for the helper and generate them, as well as tests for each one.

Usage:

> script/generate helper HelperName [methods ...]

All generated helper tests will be run with rake test now along with all the other tests. Also a rake test:helpers task is provided to run just the helper tests.

Bonus Assertions!

That’s right ... I’ve also rolled in assert_tag_in, assert_tag_not_in, assert_select_in, assert_hpricot_in, and assert_hpricot_not_in assertions to the ActionView::TestCase class. These work exactly like their ActionController counterparts, except for the Hpricot ones, of course. These work by simply passing them the HTML string, and either a CSS selector or a XPath selector. The method then looks for an element matching the selector.

In the future, I’d like to spiffy assert_hpricot_in up by making it more like assert_select but it’s still useful to all Hpricot lovers out there as-is.

The Helper Me Test plugin:

The plugin is up on GitHub: http://github.com/vigetlabs/helper_me_test/tree/master

The clone URL to install with: git://github.com/vigetlabs/helper_me_test.git

As always comments, suggestions, or whatever are welcome, as well as forking it and adding your own twist.

Helpers vs. Partials - A Performance Question

Ben Scofield
Ben Scofield, Development Director, June 25, 2008 4

Have you ever looked in your development log and seen something like the following?

Processing FoosController#show (for ...) [GET]
  Session ID: BAh7CDoOcmV0dXJuX3R...
  SQL (0.000869) SHOW TABLES
  SQL (0.000931) SHOW TABLES
  Image Load (0.000465) SELECT images.* FROM ...
  [...]
Rendered entries/_detail (0.00221)
Rendered entries/_detail (0.00103)
Rendered entries/_detail (0.00118)
Rendered entries/_detail (0.00092)
Rendered entries/_detail (0.00107)
Rendered entries/_detail (0.00069)
Rendered entries/_detail (0.00215)
Rendered entries/_detail (0.00113)
Rendered entries/_detail (0.00113)
Rendered entries/_detail (0.00098)
Rendered entries/_detail (0.00098)
Rendered entries/_detail (0.00087)
Rendered entries/_detail (0.00079)
Rendered entries/_detail (0.00086)
Rendered entries/_detail (0.00079)
Rendered entries/_detail (0.00087)
Rendered entries/_detail (0.00076)
Rendered entries/_detail (0.00081)
Rendered shared/_nav (0.00207)
Rendered shared/_analytics (0.00011)
Rendered shared/_footer (0.00188)
Completed in 1.63867 (0 reqs/sec) | Rendering: 0.40653 (24%) ...

These sorts of entries in the log have always felt like a code smell to me - even when some sort of markup reuse is obviously necessary (so you can't just do it inline), I can't help but think that there's some significant cost to rendering the same file over and over again. So after a long time of thinking about it, I finally got around to doing some benchmarks comparing generating markup in a helper method, rendering a partial repeatedly for a collection, and rendering a partial with the :collection key. For each method, I use apache benchmark to hit a page 1000 times (with 2 simultaneous requests); each request generated 1000 divs like this:

<div>
  <span>index</span>
  <a href="#">link</a>
</div>

Here's what I found:

Method Time per Request    Requests/Second   
Helper 186.998 ms 10.70
Partial 438.244 ms 4.56
Partial for collection    266.068 ms 7.52

So it looks like there is a cost to rendering a partial repeatedly, but that cost can be reduced by using the :collection key, and reduced even further by generating the markup in a helper method. Of course, the helper method can be a smell of its own, but if performance is an issue it may be worth a look.

Note: This is an unscientific test, so feel free to respond with your own benchmarks.

Announcing the ActionButton Plugin

Brian Landau
Brian Landau, Web Developer, June 19, 2008 1

When creating web application there’s often a need for single links that have a destructive or “unsafe” effect. In accordance with HTTP specs, GET’s (e.g. links) should not be used for this. The issues that 37signals had with unsafe links prove the importance of this.

This is also important if your application is RESTful. Rails offers a few good solutions, but none of them are ideal. There’s link_to_remote, but this requires the user to have JavaScript enabled and doesn’t allow for progressive enhancement. Then there’s submit_to_remote, which requires you to wrap it in a form tag yourself and also doesn’t allow for progressive enhancement. Last, you have button_to, which is almost ideal, but it uses a submit button that offers less options for styling than a button tag.

And this is where the idea for a button tag helper came in. (See this blog post for a good description of why the button element rocks). Thus, ActionButton was born.

With it, you can create a form with a single button element. The form can be set to point to any url just as you would with a normal form. The button and the form can have different id and class attributes, making them easy to target via JavaScript. But you don’t need to set these; by default it will create classes and ids that are sensible. Of course, all the normal HTML options are available, too.

The plugin will also install a modified version of the lowpro library. The ujs_remote_form helper provided creates a snippet of lowpro JavaScript that can be used to make a form (or set of forms) submit via an AJAX call.

So how would I use this?

Well, if you have a list of blog posts in an admin section and you want to add delete buttons next to each of them and you want them to look the same in every browser, this is how you would do it.

<%= action_button 'delete_post', "#{image_tag('icons/delete.jpg', :alt => 'delete')} Delete", 
post_url(post), {:method => :delete, :number => post.id} %>

Then when you’re ready for some AJAXy goodness, you can either use the Rails built-in helper of the plugin’s lowpro one:

Built-in:

Add this for each post:

<%= observe_form "delete_post#{post.id}-form", :on => 'submit', 
:confim => 'Are you sure you wish to delete this post?' %>

Lowpro:

Add this for the whole page of posts:

<%= ujs_remote_form 'button.delete_post', :confirm => 'Are you sure you wish to delete this post' %>

So where do I get this plugin of button joy?

It’s up on github right now!

If you have any questions, comments or suggestions, send them my way. It’s great to get feedback, but if you really feel I messed something up, just fork it and make the changes you feel it needs.

Inheritance is Unlikely

Justin Marney
Justin Marney, Web Developer, June 05, 2008 8 What do you do when you identify common sets of shared behavior across several classes?

A common solution is to move the shared code into a base class and inherit.
# Filled with api interaction behavior
class Base
  def api_specific_behavior
    puts "connect to api"
  end
end

class User < Base
  def user_specific_behavior
    puts "I am a user"
  end
end
The problem with inheritance in this situation lies primarily in a subtle violation of the Liskov Substitution Principal which essentially states that inheritance should implement an "is-a" relationship. If we have a class called User that inherits from Base, are we trying to model the part of our domain where a User is a Base? This doesn't make sense given that we never actually have any Bases (i.e. instances of Base).

Continue reading "Inheritance is Unlikely"

Testing for HTML Tags in Rails Plugins

Brian Landau
Brian Landau, Web Developer, June 04, 2008 5

When creating Rails plugins that add ActionView helpers, we often test to ensure they produce specific tags with specific attributes. When testing this type of assertion within controller tests, we have the very useful assert_tag and assert_select; but in plugin tests and elsewhere, these aren't available. Adding this functionality to your own tests turns out to be somewhat convoluted.

The first realization you come to is that assert_tag and assert_select can't be used because they test against the response body of a controller. Since we're trying to test functionality independent of other components, using ActionController when it's not necessary is not recommended. You might be tempted at this point to just test against a regular expression and forget using these methods. That is doable, but a little unmanageable in the long run. First off, attributes can't be expected to be in a specific order, and their order doesn't matter, so you don't want to test for that. This will even initially make your regexes fairly complicated. Here's an example of testing for a specific name attribute:


def test_for_name_attribute
   tag = text_field_tag('login')
   assert_match /<input\s+(([\w^(name)]+="([^"'><]+)?"\s+)+)?name="login"((\s+[\w^(name)]+="([^"'><]+)?")+)?(\s+)?\/>/, tag
end

As you can see, even matching one attribute can result in a extremely long and ugly regex. What if we want to match multiple attributes and nested tags? This quickly becomes untenable.

If you look to include the component used by assert_tag to find and match specific tags, you will find require 'html/document'. Try just requiring this by doing this at the top of your test:


require 'test/unit'
require 'rubygems'
require 'active_support'
require 'action_view'
require 'html/document'

You'll find you get a no such file to load -- html/document (MissingSourceFile) error. If you dig around in the Rails code, you'll find it lives in action_controller/vendor/html-scanner. To be able to include html/document, you need to first include action_controller.

Now that we have html/document included, let's make a test helper method to match a tag specification. This takes little change from the original assert_tag method.

def assert_tag_in(*opts)
   target = HTML::Document.new(opts.shift, false, false)
   opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
   assert !target.find(opts).nil?, 
     "expected tag, but no tag found matching #{opts.inspect} in:\n#{target.inspect}"
end

With this, you can now use assert_tag_in to do the same test as above:

def test_for_name_attribute
   tag = text_field_tag('login')
   assert_tag_in tag, :input, 
     :attributes => {:type => 'text', :name => 'login'}
end

The whole code for a sample test_helper.rb file for plugins can be found in this pastie.
Also, look at the documentation for assert_tag for all the options available.

From this, an assert_no_tag_in could easily be made. You could use a similar technique to make a assert_select_in, although assert_select is much more complex and often overkill for simple helpers where you aren't testing as large a number of tags or as complicated as nesting.

Update:

Given the interest in a hpricot version of the assert_tag_in, I've put together one:

def assert_tag_in(target, match)
   target = Hpricot(target)
   assert !target.search(match).empty?, 
   "expected tag, but no tag found matching #{match.inspect} in:\n#{target.inspect}"
end

With this you can then write a similar test like this:

def test_for_name_attribute
   tag = text_field_tag('login')
   assert_tag_in tag, 'input[@name="login"]'
end

The alternative test_helper.rb using hpricot can be found in this pastie.

A Development Community for Viget Labs and Beyond

Every team member here at Viget Labs strives to be an innovator. We members of the development team are no different - that's why we're constantly engaging in community discussions and exploring the unknown that is the next generation of open-source web applications.

Viget Is Hiring!

Viget has job openings for Ruby Developers, Interns, and Front-End Developers. Learn More »

Upcoming Events

O’Reilly’s Open Source Convention - July 21 - 25
Clinton R. Nixon, our other Senior Developer, will be speaking on "Extending Rails: Understanding and Building Plugins."

Recent Comments

Smashing! Thanks for outlining what’s needed to be done so precisely :-)