Loving Rails' functional testing

The one thing I’ve never quite managed to get the hang of in ASP.NET development has been writing functional tests. Those that can exercise a control within it’s GUI environment. I’ve used a couple of tools, to various depths and with varying success.

For instance, with NUnitAsp it’s possible to write the functional tests directly within Visual Studio. This to me shortens the feedback loop since I don’t need to go away and author elsewhere—it fits in nicely with the “test > fail > code > pass” TDD process I use elsewhere. However, it’s not particularly quick to get up and running and it depends on you having a webserver up and running (although I’ve seen some neat stuff to improve some of this). However, you’re still at the whim of ASP.NET’s lifecycle and core framework constraints that make testing outside of a real Page container and away from an HttpContext nigh on impossible (from what I’ve seen and tried anyway).

In contrast, functional testing is supported directly in Rails - indeed it’s actively encouraged - when you generate a Controller class, it automagically creates you a mytype_controller_test.rb stub that you can get to work in straight away.

However, the real magic (and joy) comes from actually writing the tests. For instance, take a look at this code from one of the tests I have for a login controller:

def test_login_with_valid_user  post :login, :user => {:name => "paul", :password => "p4ssw0rd"}end

assert_response :successassert_not_nil session[:user_id]

This posts to the :login action on the controller, and passes into it’s parameters a constructed user. It then checks that the response is a success (HTTP response code 200) and that there is a :user_id set in the session.

To support this, I do the following inside the controller’s login action:

@user = User.new(params[:user])logged_in_user = @user.try_to_login

if logged_in_user  session[:user_id] = logged_in_user.idend

Now, that is good in itself - I’m testing that the user is stored in the session as a result of a successful login. However, I also want to test the GUI - that is that an error message is displayed following an invalid login. And this is where it gets really beautiful:

def test_login_with_invalid_user  post :login, :user => {:name => "baduser", :password => "p4ssw0rd"}end

assert_equal "Invalid user/password", flash[:notice]assert_tag :tag => "div",  :attributes => {    :id => "flashNotice"   },  :child => {    :content => "Invalid user/password"   }

In this test, I want to make sure that when I submit the form there is a div that has the id flashNotice and inside has some inline content informing the user of the failure. The thing that’s really striking is just how neat and tidy it is.

A while ago we were fortunate to have Charlie Poole working with us to help improve our agile ways. During that time (since we do a lot of web work) acceptance/customer testing was a big concern. Previously we’d used a customised version of the Exactor framework. Although this worked, it was essentially

ClickButton "Really_long_aspnet_id" SetText "Another_long_id" ...

These “commands” mapped onto C# classes, that looked something like

public class ClickButton : WebCommand {  public override void Execute() {    ...  }}

We ended up having various commands for looking for buttons by id, by label etc. This then repeated itself across different tags. The result was a lot of duplication—and something that Charlie helped us to improve. We made changes to end up with a kind of composite tag matcher that could let us chain together criteria to find controls, also alleviating problems with the long (and somewhat brittle) control IDs that ASP.NET generates. However, we still ended up having to write a hell of a lot of code to do essentially a simple job.

The end result was a pretty comprehensive framework for testing Web GUIs, but, it needed a lot of effort to write tests in. Crucially, it didn’t really lend itself to test first in the first instance, and then maintenance became difficult. Ultimately we (shamefully) started to skimp on tests.

One crucial difference of note is that with Exactor we were after tests that non-developers could write, or that our customer team could write with customers, so they are for different purposes.

I’ve only been using Ruby and Rails for a couple of weeks now, but this alone is a major win for me (and one I’ve only just started to get to grips with). I can construct high-level functional tests that can test both under and through the GUI, testing that the controllers and views do their thing with data and/or presentation, all from within an elegant test fixture. That’s really neat!