• ICandy

At my day job we have several Rails apps that we've built and continue to maintain and enhance. We often add features that are usable in all of our applications, so although they're often initially developed in one app or another, they quickly get turned into plugins that we can share across all of our apps.

My boss and fellow Rails developer Aaron Baldwin has been typically hacking on plugins that are strictly internal and wouldn't be of much use to anyone outside of our organization, whereas I've been fortunate enough to get to work on things that are more generic and thus releaseable to the broader community. So, it's particularly noteworthy that Aaron released his first public Rails plugin this week, and this is one of those plugins that, now that I've used it, I don't know how I could have ever coded Rails without it.

Here's what it's for. When writing integration tests you often test that certain elements exist on the page. The problem though, is that when one of these tests fail, all you're told is that an element of some type was expected, but none were found. Was the element really not there? Or is there a typo in your test? Or on the page? Or... what? I've found myself in this scenario desperately wishing I could just see the page that the test was testing in my browser window so I could look for myself.

Enter ICandy. ICandy is a Ruby on Rails plugin that automatically displays the page from failing integration tests in your browser window. You can also manually trigger certain pages to open in your browser, if you desire. It's been tested to work with Safari and Firefox on Mac and Firefox on Ubuntu.

Comments: 0 [add comment]

I've just released ARID 0.5. This is a minor update with some small bug fixes and optimizations.

ARID ( ActiveResourceIntegrationDsl ) is a Ruby on Rails plugin offering easy to use methods for writing integration tests on RESTful Rails apps. With ARID you write simple statements like user.builds_article(:params => {:article => {:subject => 'Hey!', :content => 'Hello World!'}}) which will GET your new_article_path, check that it has a properly formatted form with fields for all the given params, and then will POST the params to articles_path and assert the expected response. You can even pass a block to the above call to perform additional tests. And YES, ARID tests AJAX calls.

More info and installation instructions at http://arid.rubyforge.org.

Comments: 0 [add comment]

ARID has a new home of it's own on RubyForge, and version 0.4 and improved documentation is now available.

ARID (ActiveResourceIntegrationDSL) is a Ruby on Rails plugin providing methods for simple and DRY integration testing of conventions-compliant RESTful Rails applications.

This version contains significant changes to how test methods are formatted and is NOT backwards compatible with tests for prior versions.

Changes

  • :expected_response option is now just :expects
  • creates_ and updates_ methods no longer go through the new and edit actions.
  • added builds_ and edits_ methods that do continue on to creates_ or updates_ if passed a :params hash.
  • renamed reads_ method to shows_ and aliased lists_ to shows_.
  • renamed deletes_ to destroys_.

Bug Fixes

  • significant updates to previously sparse documentation, now RDoc-able.

Ehancements

    added option to add HTTP headers to requests
  • added support for textarea and select fields
  • added support for nested resource paths

This version is currently being used in several production Rails 1.2.X apps and one Rails 2.0RC1 app still in development.

Comments: 0 [add comment]

I just released version 0.2 of ARID (ActiveResourceIntegrationDsl) [formerly known as RestfulCrudIntegrationTester]. I've been working with this on three different projects for the past week and so far am very happy with it.

Besides the name change, there's a couple major enhancements.

A new sess.exercises_ method.

This allows one call that runs through all the CRUD actions of a RESTful controller, assuming the controller doesn't do anything weird. So something like

napoleon.exercises_llama({:name => 'Tina'},{:name => 'Trisha'})
is equivalent to the 0.1 method of...
napoleon.creates_lama({:name => 'Tina'}) do |page|
  @llama = page.assigns(:llama)
end     
napoleon.reads_lama(@llama)
napoleon.updates_llama(@llama,{:name => {Trisha'})
napoleon.destroys_llama(@llama)
which runs through a big chunk of a simple RESTful controller's code and views checking that forms are all setup correctly on new and edit pages and that the controller properly processes the submitted forms and respond with :success on reads and :redirect everything else.

It now handles AJAX calls.

Simply add {:via_ajax => true} to the options hash for the lower level goes_to (GET), posts_to and puts_to functions, and it'll pass it to your controller as an XML_HTTP_REQUEST, instead of a standard HTTP. It'll assert a :success, but It's up to you to check for anything else in the response. i.e.

napoleon.puts_to(llama_path(@llama),{:name => 'Pedro'},{:via_ajax => true}) do |page|
  assert_equal('Pedro',page.assigns(:llama).name)
  page.assert_select_rjs :replace_html, :llama
end 

Comments: 0 [add comment]

When working with Rails full time, it's easy to grow accustomed to the fact that when you do things right, other things "just work" without you doing anything else.

Case in point, I just got ready to see what I needed to do to make my RestfulCrudIntegrationTester work with nested routes. First thing to do was to write an Integration test using the plugin that tried to go to a nested route and see what errors I got. As I was half expecting, I didn't get any errors. I checked the test log, and sure enough, the controller reported that it got all the expected parameters and returned the correct page. So, here's what the routes and test look like, as a demo.

map.resources :posts do |users|
  users.resources :comments, :name_prefix => 'post_'
end
@post = Post.find(3)
new_session_as('me','super_duper_pass') do |me|
  me.reads_post_comments(@post)
end

And that's it. The resulting param hash at the comments controller is {"action"=>"index", "controller"=>"comments", "post_id"=>"3"} showing that the GET request went to '/posts/3/comments' as expected.

Tangent Alert

One note, you'll see that I used the :name_prefix option on the route above. My boss and I went back and forth on this for a while this week.

He was in favor of using it, as it makes your helper methods a little more descriptive (or so he claimed). With it, instead of comments_path(@post), you say post_comments_path(@post). My argument was that it's just as clear what comments_path(@post) does once you get used to it, it's less typing, and its convention over configuration (where adding :name_prefix to routes.rb would be extra configuration, overriding convention, and counter to the Rails philosophy). If we've learned anything this past year, it's that whenever you find yourself fighting the framework, you're doing something wrong.

This went on for a while, each of us steadily getting frustrated with the other's obstinance, and since we both work on the same code, it was imperative that we reach a consensus. "Agree to Disagree" was not an available option. One of was going to have to give in.

Then, out of the blue, he pulled a trump card out of Google. In Rails 2.0, adding the prefix onto the helper methods would be default behavior, and if you didn't want them, you'd need to add :name_prefix => nil in your routes to turn it off. Well, clearly, removing a few :name_prefix => 'something_' entries from the routes after upgrading would be a lot easier than searching through the code trying to find all the references to the helper methods and fixing those. So, adding :name_prefix now is just temporary configuration, in preparation for future convention.

In the end, I think we both won. He got his name prefixes, and I got my convention. And the point the story? Start using :name_prefix in your nested routes now, and save yourself some aspirin down the road.

Comments: 0 [add comment]

Took a little time today to give the site a makeover, and fix some small bugs. I still don't claim to have any eye for aesthetics, but I think this probably looks a lot nicer than before. Clean & simple.

I also released my first public Rails plugin this morning (with the wildly clever name of RestfulCrudIntegrationTester), a whole 36 hours sooner than I thought I would yesterday.

I've just finally started delving into Integration Testing (yah, I know, I'm a little late to the party.) I was writing tests for two different projects (one at home and one at work) and noticed even though they were very different projects, I was duplicating a lot of low level integration test code. So... I extracted it out into a plugin.

Now, on both projects, and any others I use the plugin in, I can just say things like...
new_session_as('jon','Exc3l1ent_p@s5w0rd!') do |jon|
  jon.updates_article(1,{:article => {:subject => 'New Subject'}})
end
And then the Plugin will go try to login as that user (using the sessions_controller), and exercise both the edit and update actions in the articles_controller, as well as confirm that the edit form has the correct action, method, and input elements for a RESTful update.

Note, that the plugin is only for very RESTful controllers (if you didn't get that already), and it makes a few big assumptions on how your Rails app works. I tried to make sure the assumptions followed Rails conventions as much as possible.

Some of the initial code came from Jamis Buck's post on the subject from over a year ago, (yah, yah, late to the party) so thanks to Jamis for getting me started on the right path.

Strangly, as long as Integration Testing has been around, I didn't find any existing plugins to provide this kind of basic framework. So, I'm hoping I didn't waste time reinventing the wheel, and instead have found an unfilled need and provided a worthwhile contribution to the Rails community. Time will tell on that.

Here's the complete README file from the 0.1 version of plugin.

RestfulCrudIntegrationTester
============================
Provides a framework of common integration
tests for RESTful CRUD interactions.

How To Use
==========
class ArticleTest < ActionController::IntegrationTest
  include RestfulCrudIntegrationTester
  
  def test_some_article_functionality
  	new_session_as('jonnyg','testing123') do |jon|
      params = {
      	:article => {
      		:title => 'Foo Bites Bar',
      		:content => 'Bar was bitten by Foo yesterday.' 
      	}
      }
      jon.creates_story(params)
      params = {:article => {:title => 'Bar Bitten Badly'}}
      jon.updates_story(1,params)
      jon.reads_story(1)
      jon.deletes_story(1)
    end
  end
end

Assumptions
===========
The plugin makes certain assumptions about your application.
Some of these can be bypassed or overridden if your app does
not conform.

* you have appropriate map.resources lines in your routes.rb
	for any controllers you intend to test with these methods.
* sessions_path helper method returns the path to a controller
	that controls user logins. (Provided automatically by
	Rails if you have a sessions_controller and routes.rb
	includes "map.resources :sessions".)
	@ Override by adding your own sessions_path method to
	application_helper
	
* your sessions controller accepts parameters in the form of...
	{:user => {:username => 'xxx', :password => 'yyy'}}
	@ Override by providing your own new_session_as method
		that generates an appropriate session for your app
		with the user provided.

Provided Methods
================
-Create Sessions-
-----------------
new_session:
	creates generic session (i.e. user not logged into your site).
	
	#example
	new_session do |guest|
		#guest.does_things here
	end
	
new_session_as:
	creates session as user with provided username and password.
	
	***assumes that you have a 'sessions_controller' and that it
	takes params in the form of... 
	{:user => {:username => 'xxx', :password => 'yyy'}}
	
-Do CRUD-
---------
All CRUD tasks can be passed a block to do additional assertions
sess.reads_():
	performs a GET to _path()
	asserts a :success response
	yields if you passed a block containing more assertions
	
sess.creates_():
	performs a GET to new__path
	asserts a :success response
	asserts page contains a form with a _path action
		and a 'post' method.
	asserts form contains appropriately named input elements
		for each of the  you provided.
		i.e. 'person[first_name]'
	performs a POST to _path passing 
	yields if you passed a block containing more assertions
	if you did NOT pass a block
		asserts a :redirect response
		follows redirect
		asserts a :success response
	
	
sess.updates_(,)
	performs a GET to edit__path()
	asserts a :success response
	asserts page contains a form with a _path action
		and a 'post' method.
        asserts form contains a hidden input field named '_method'
    	        and a value of 'put'.
	asserts form contains appropriately named input elements
		for each of the  you provided.
		i.e. 'person[first_name]'
	performs a PUT to _path passing 
	yields if you passed a block containing more assertions
	if you did NOT pass a block
		asserts a :redirect response
		follows redirect
		asserts a :success response
		
sess.deletes_():
	performs a DELETE to _path()
	yields if you passed a block containing more assertions
	if you did NOT pass a block
		asserts a :redirect response
		follows redirect
		asserts a :success response
Comments: 0 [add comment]