We are a user experience design and software development firm
Hire us to design your site, build your application, serve billions of users and solve real problems.
I'm working on a small project and trying to use RSpec as the complete testing solution. First thing I did was copy over an existing custom FormBuilder which puts everything in a nice tabular layout. Then I tried to convert the existing Test::Unit tests to RSpec specifications.
I banged my head against this for a few hours, at least partly because the normally-infallable Google-based tech support failed to expose a clear example. So I figure I should at least get a blog post out of it.
The form builder is technically a helper, so it goes in app/helpers, and the test goes in spec/helpers. One problem -- the custom form builder expects to have access to template object and calls content_tag on that object to format the output. Normally, the template object is automatically placed in @template during evaluation. But since RSpec is testing just the helper, and not an associated controller or view, there is no template object.
At first I created a mock template object that just imported the relevant Rails helpers, but then I actually read the documentation in more detail and learned that the RSpec HelperExampleGroup class, which manages helper tests, already imports all those helper modules. So, I can set up my specs like this.
before(:each) do
@object = mock_model(Project)
@object.stub!(:longname).and_return("Long Name")
@builder = TabularFormBuilder.new(:project, @object, self, {}, nil)
end
The first two lines of this just set up a mock object. The key line here is the last one, where the form builder is created with arguments representing the model, the instance, the template, and then options. The template is represented by self, meaning the RSpec HelperExampleGroup, which will respond to content_tag.
Now I was able to write all my specs against the template and use the have_tag and with_tag predicates to validate the output, like so:
it "should build input fields" do
@builder.text_field(:longname).should have_tag("tr") do
with_tag "td.tdheader"
with_tag "label[for *= project_longname]", "Longname"
with_tag "td" do
with_tag "input.text_field#project_longname[name *= longname][size = '30'][value = 'Long Name']"
end
end
end
One other nice feature of the helper specs in RSpec is the ability to directly evaluate ERB. This is designed to be used for validating helpers that take ERB blocks as arguments. Use as follows:
eval_erb("<% table_form_for (@object) { |f| f.text_field(:shortname) } %>")
The method being tested looks like this -- it wraps a form inside an HTML table so that the custom form builder's table layout will render properly:
def table_form_for(name, *args, &proc)
concat("<table>", proc.binding)
form_for(name, *convert_args(TabularFormBuilder, args), &proc)
concat("</table>", proc.binding)
end
The eval_erb method returns a string, which you can then validate. Note that if you are using it to test a form_for derivative like this, you also need to place the following stub method in your spec file's describe block:
def polymorphic_path(args) "http://a.fake.url" end
The polymorphic_path method is used within Rails to convert something like form_for(@object) to the RESTful URL that the form should be submitted to. Normally it's an method of the controller, but in the RSpec helper environment, there is no controller.
There's another issue when validating this form builder. Unlike a typical ERB block, the custom form builder implementation of f.text_field explicitly uses the template object to write output. The eval_erb method does not seem to work as I expected in this case -- the output from inside the block does not appear to be saved.
In other words, when running the eval_erb call above, the resulting string contains the table tag created by the table_form_for method, and the form tag created by form_for. The table rows that are built by the custom form builder itself, however, do not show up in the output. I was able to verify that the output is being generated, so either it's not being stored by the RSpec object, or I still haven't completely figure out how to tie these things together yet.
If you found this example helpful, you might also enjoy my upcoming book Professional Ruby on Rails, scheduled to be published in February, 2008.
Topics: Ruby on Rails, Test Driven Development, Tutorials
Hire us to design your site, build your application, serve billions of users and solve real problems.
Great post!
You’re totally right, there aren’t enough articles about how wonderful RSpec is. I’m hoping that the Rails team may eventually make RSpec a first-class citizen, and perhaps the default testing framework for Rails.
That said, I have nothing against Test::Unit, but after having worked with RSpec a while now, I just seems so much more expressive and declarative.
Comment by Chris, Monday, March 10, 2008 @ 6:01 am
Great article, thank you!
This made testing my custom form builder tag much easier. However, for some reason, my custom form tag wasn’t getting the object from the Builder. I had to do something like this:
f.custom_field :shortname, :object => @objectThis got it working in Rspec. Thanks!
Comment by Nate Clark, Tuesday, July 15, 2008 @ 5:06 pm
This post really helped me out getting started spec-ing FormBuilders. Many thanks.
Comment by Alister Lee, Wednesday, August 13, 2008 @ 8:36 am
Thanks for this, Noel! I was having trouble figuring out how to initialize my form builder in the spec, and this cleared it right up.
Comment by Brandan Lennox, Thursday, December 18, 2008 @ 3:41 pm