For a while now I've been writing RSpec specifications to test Expectnation, rather than "traditional" Rails unit tests. Although I don't completely get the Behaviour Driven Development hype around RSpec, I do find that it helps in writing more literate and maintainable test suites.
Until very recently, however, RSpec for Rails had no decent answer to Rails' integration tests. Integration tests enable the testing of compete scenarios, as opposed to the specific testing of methods or single HTTP operations that unit and functional testing provide.
For instance, our integration test suite for Expectnation tests things such as "a user tries to submit a proposal, is redirected to the account sign up page, signs up, and then is redirected to the proposal submission page". This involves multiple steps, and the whole suite of tests necessitates a lot of reuse.
The need for reuse resulted in trying to construct a DSL-like set of methods for each step, such as "user logs in with email X and password Y". It has gotten a bit tangled, especially when looking at test failures to see what's really gone wrong.
My problems with integration tests made the Story Framework, newly available in RSpec 1.1, look very interesting. Based around plain text descriptions of application behaviour, it lets you write integration tests with good reuse and good diagnostic reporting.
For example, here's a story I wrote to check the login process.
Story: login as an existing user
As an unauthenticated user
I want to log in to Expectnation
So I can see my account details
Scenario: login details are correct
Given an event provider
And my test@example.org account
When I log in with email test@example.org and password foofoo
Then I will be logged in
And I will be shown the account page
The words such as "Given", "When" and "Then" are cues to the story runner to execute some code. Behind the story sits a collection of steps. Here's a couple of steps from this test:
Given "my $email account" do |email|
@user = find_or_create_user_by_email({:email => email,
:password => 'foofoo',
:password_confirmation => 'foofoo'})
end
When "I log in with email $email and
password $password" do |email, password|
post '/user/account/authenticate',
:user => {:email => email, :password => password}
end
Notice how a clever bit of string matching allows you to pass parameters from the story prose.
With a small bit of bolting together, the prose stories are then run as code and the tests executed. One nice touch is that if you write a story which doesn't yet have code behind it, it is marked not as a pass or fail, but as pending, which means you can sit down and write all your stories and then get to coding the steps later. This beats writing empty tests which transparently pass.
To conclude, the RSpec Story Framework offers these advantages over Rails' integration tests: