February 28, 2011

Incorporating ActionMailer in Cucumber Tests

It is very common for Ruby applications to send transactional e-mails to users. What's not immediately obvious is how, as developers, we can test this functionality using our tools of choice: Cucumber and Rspec.

Ideally, the method should be re-usable so that we can simply add a single step to our scenarios, e.g:

And it should send me an "account confirmation" e-mail

One way to do this in a re-usable way is to add a new tag, @email, which sets up ActionMailer. In your features/support/env.rb file, add the following:

Around('@email') do |scenario, block|
  ActionMailer::Base.delivery_method = :test
  ActionMailer::Base.perform_deliveries = true

This ensures ActionMailer is in test mode (all e-mails will be added to an array we can inspect, rather than sent out to the real world), and it clears any other e-mails in the deliveries array before each step is executed. Now we can simply add the @email tag before any scenario which needs to test e-mail functionality and this will be done for us automatically.

Then we create a new features/step_definitions/email_steps.rb file and add the following step definition:

Then /^it should send me an? "([^\"]*)" e\-mail$/ do |arg1|
  @email = ActionMailer::Base.deliveries.last
  @email.to.should include @user.email
  @email.subject.should include(arg1)

This will test the subject line against the argument "account confirmation" in your scenario, so we could use this step for other types of e-mail; e.g. "password reminder".

We can also test that the application doesn't send an e-mail, for example:

Then /^it should not send an e\-mail$/ do
  ActionMailer::Base.deliveries.size.should eq 0

Here's an example of the e-mail testing functionality in action:

Scenario: Creating an account
  Given I am logged out
  Then I should be able to register a new user account
  When I enter my details
  Then it should create a new account for me
  And it should send me an "account confirmation" e-mail

I hope this helps someone!