Thursday 6 June 2013

Testing Mailers

The first thing to check when testing your project sends e-mails is that your project is not sending them when you are testing. Rails does this by default by a setting in config/environments/test.rb:

  config.action_mailer.delivery_method = :test

Compare to the line in config/environments/development.rb

  config.action_mailer.delivery_method = :smtp


Setting this to test stops e-mails being sent, and instead they are sent to an array, ActionMailer::Base.deliveries. This array is reset before each test, by the way.

Here is a simple mailer to test.


class Notifier < ActionMailer::Base
  default :from => "DB@mysite.com"
  add_template_helper(ApplicationHelper)
  
  # Sets up an e-mail for notifying the user to activate his account.
  def signup_notification user
    @name = user.username
    @login = user.login
    @url  = "http://mysite.com/activate/#{user.activation_code}"
    mail :to => user.email, :subject => 'Activate your account'
  end

  def page_error(err, request)
    @err = err
    @request = request
    mail :to => 'admin@mysite.com', :subject => 'Page error'
  end
end

In your controller, you might invoke the first like this:

  Notifier.signup_notification(@user).deliver

In the test, you will need to break that up, so you can examine the mail object.

class NotifierTest < ActionMailer::TestCase
  test "signup_notification" do
    # Create a mock user
    user = TestUser.new 'tester', 'tester@nowhere.com'
    # Invoke the mailer method
    mail = Notifier.signup_notification user
    # Deliver the mail
    mail.deliver
    # Check the mail got sent
    assert !ActionMailer::Base.deliveries.empty?
    # Check it is the right mail
    assert_equal 'Activate your account', mail.subject
    assert_equal ["tester@nowhere.com"], mail.to
    assert_equal ["DB@mysite.com"], mail.from
    assert_match "Visit this url to activate your account",
                    mail.body.encoded
  end
end

Here is the TestUser definition.

class TestUser
  attr_reader :username, :email, :login

  def initialize username, email
    @username = username
    @email = email
    @login = username.gsub ' ', ''
  end
  
  def activation_code; "abcd"; end
end

The second method in the mailer above is for sending error reports to the administrator. Here is a method that generates an error, and sends that to the mailer:

  test "page_error" do
    request = TestRequest.new
    begin
      raise "A test error"
    rescue Exception => err
      mail = Notifier.page_error(err, request)
      mail.deliver
      assert !ActionMailer::Base.deliveries.empty?
      assert_equal "Page error", mail.subject
      assert_equal ["admin@mysite.com"], mail.to
      assert_equal ["DB@mysite.com"], mail.from
      assert_match "error encountered!", mail.body.encoded
    end
  end

Here is the TestRequest definition; it simply returns the string "good" if the method name is recognised - that is enough for the mailer to wok with, and will still highlight any mistyped or made-up method names.

class TestRequest
  # Returns the string "good" if the method is recognised
  def method_missing method, *args
    return "good" if [:fullpath, :request_method, :query_parameters,
                      :request_parameters, :referer].include? method
    super  
  end
end
end

No comments: