Monday, April 2, 2012

Unit testing emails in Rails using RSpec

In this post I am using a specific example of a controller action that sends an email. However, the outlines of the testing approach should be applicable in a wider variety of cases.

Setup


1. app/controllers/users_controller.rb:

class UsersController < ApplicationController

  def signup
    email = params[:email]
    ...
    ...
    UserMailer.signup(email).deliver
  end
end

2. app/mailers/user_mailer.rb:

class UserMailer < ActionMailer::Base
  default from: "sender@domain.com"

  def signup(email)
    mail(:to => email, :subject => "Welcome!")
  end
end

3. app/views/user_mailer/signup.html.haml (contents of the email):

A quick brown fox jumped over the lazy dog.

There are three parts to writing comprehensive specs for this setup. The first part, corresponding to the controller, should test that the controller indeed sends the correct email. The second part, corresponding to the mailer, should test that the email is set up correctly, i.e, it has the correct sender, recipient, subject and so on. The third part is about testing the contents of the email itself, corresponding to the email view.

Tests

Part I: Is the correct email being sent? (Controller)

In spec/controllers/users_controller_spec.rb:

require "spec_helper"

describe UsersController do
  describe "POST signup" do
    it "sends a verification email." do
      email = random_email

      post :signup, email: email
      expect_email UserMailer.signup(email)
    end
  end
end

The bolded part is key. It is a simple helper method that tests that the email has all the expected headers, since headers define the email.

P.S. "random_email" is another helpful method that I end up using frequently.

My implementation of the helper method, which I define in spec/support/email.rb is as follows:

def expect_email(email)
  delivered = ActionMailer::Base.deliveries.last
  expected =  email.deliver

  delivered.multipart?.should == expected.multipart?
  delivered.headers.except("Message-Id").should == expected.headers.except("Message-Id")
end

The test excludes "Message-Id" from the headers, since that is going to be different for every instance of otherwise exactly identical emails.

Part II: Does the email have the right headers? (Mailer)

In spec/mailers/user_mailer_spec.rb:

require "spec_helper"

describe UserMailer do
  describe ".signup" do
    let(:recipient) { random_email }
    let(:email) { UserMailer.signup(recipient) }

    it { email.from.should == [ "sender@domain.com" ] }
    it { email.to.should == [ recipient ] }
    it { email.subject.should == "Welcome" }
  end
end

The above should be fairly self explanatory.

Part III: Does the email have correct contents? (View)

This is no different from any other view tests, therefore I have omitted this part.

I hope you found this post useful in keeping your code lean and clean.

No comments: