I've always found an excuse to do very minimal or no testing in the rails apps I write. Mostly (so I tell myself) it's because I write them all by my lonesome, so it's faster to just write it than to test it, and I trust my own code (who doesn't).
Recently though, the development team at Shuntyard has been growing and I find myself in need of some peace of mind when it comes to the code base.
The devs are good, or they would not be working with us in the first place. No, the tests are not so much for testing broken code as it is for testing broken functionality. You see, developers like to solve problems in different ways. Often simply on principle. So, when someone "cleverly" refactors a perfectly fine piece of code, we need to know if it breaks something down the line.
Enter RSpec.
I an not too concerned with unit tests or testing controllers. I trust these work well enough to get the job done, if the job gets done. So what I want to test is application behaviour. Can I log in ? Can I change my profile? Do I see a list of things when I click on the right links ? So, what I'm realy after is acceptance testing.
Enter Capybara.
There are quite a few tools out there to use for testing Rails apps. They al have merit (I presume) or they would not exist. For the testing of Wami though, I opted for something that cut close to the core, and did not abstract the testing too much.
I also make use of factory_girl_rails to construct models, instead of the default fixtures.
In a land before time...
So, on to the testing. I decided to start from scratch, so that I can see exactly how it all fit together .
First off, I added the gems for rspec, capybara to my Gemfile. I already had guard in there, having started this quest for fire on railscasts.com, guard seemed like a good tool. So here is the relevant part of my gemfile:
gem 'rspec-rails', :group => [:test, :development]
group :test do
# Pretty printed test output
gem 'turn', :require => false
gem 'capybara'
gem 'factory_girl_rails'
gem 'guard-rspec'
end
The next step is to install rspec properly into the app:
$ rails g rspec:installidentical .rspec
exist spec
create spec/spec_helper.rb
Don't forget to wire capybara in, by adding this to your spec_helper:
require 'capybara/rails'Now, to test that a user can sign up to my site. We use devise for authentication, but that is irrelevant to the test, as I'm testing behaviour only.
To do this, I need to create a spec for signing in.
$ rails g integration_test signup
invoke rspec
create spec/requests/signups_spec.rb
Automatic, sort of
Ok, here is where the headaches started. Guard, for all it's automated goodness, was not running my migrations when I added fields to models. The errors it gives as a result are also not too untuitive.For instance, I added a cellnumber to my model, even before I first ran guard.
Failure/Error: user = Factory(:user)
ActiveRecord::UnknownAttributeError:
unknown attribute: cellnumber
# ./app/models/user.rb:31:in `new'
# ./app/models/user.rb:31:in `create_primary_cell'
# ./spec/requests/sign_ins_spec.rb:5:in `block (2 levels) in
At some point I had apparently migrated my test database, but before I added this field with a migration.
I eventually figured that the migrations was the problem, when I manually ran a
$ rake spec:requests
and saw the migrations run explicitly there. Starting guard up after that got through most of the tests, at least past the stumbling block.
So, remember to migrate your test DB also!
The ugly...
... part about RSpec is the counter intuitive DSL. I suppose it's hard to get it right, and there is a good deal of helpers around, but I'm yet to see a concise list of valid rspec matchers...
I'll deal with them in separate posts as I discover them.
In the meantime, where are my two tests:
describe "SignIns" do
it "signs in a valid user" do
user = Factory(:user)
user.confirm!
user.confirmed_at.should be_within(1.minute).of(Time.now)
visit user_session_path
fill_in "user_email" , :with => user.email
fill_in "user_password" , :with => 'secret'
click_button "Sign in"
current_path.should eq(root_path)
page.should have_content("Signed in as #{user.email}")
end
end
it "signs in a valid user" do
user = Factory(:user)
user.confirm!
user.confirmed_at.should be_within(1.minute).of(Time.now)
visit user_session_path
fill_in "user_email" , :with => user.email
fill_in "user_password" , :with => 'secret'
click_button "Sign in"
current_path.should eq(root_path)
page.should have_content("Signed in as #{user.email}")
end
end
describe "PasswordResets" do
it "emails users when requesting password reset" do
user = Factory(:user)
visit user_session_path
click_link 'password'
fill_in "Email" , :with => user.email
click_button "reset password"
current_path.should eq(user_session_path)
page.should have_content("You will receive an email with instructions about how to reset your password in a few minutes")
last_email.to.should include(user.email)
end
end
it "emails users when requesting password reset" do
user = Factory(:user)
visit user_session_path
click_link 'password'
fill_in "Email" , :with => user.email
click_button "reset password"
current_path.should eq(user_session_path)
page.should have_content("You will receive an email with instructions about how to reset your password in a few minutes")
last_email.to.should include(user.email)
end
end