42

I am developing a Rails 4 application which involves sending / receiving emails. For example, I send emails during user registration, user comment, and other events in the app.

I have created all emails using the action mailer, and I used rspec and shoulda for testing. I need to test if the mails are received correctly to the proper users. I don't know how to test the behavior.

Please show me how to test an ActionMailer using shoulda and rspec.

Peyman Mohamadpour
  • 17,954
  • 24
  • 89
  • 100
Dinkaran Ilango
  • 513
  • 1
  • 4
  • 11

1 Answers1

67

How to test ActionMailer with RSpec

  • works for Rails 3 and 4
  • this information has been taken from a good tutorial

Assuming the following Notifier mailer and User model:

class Notifier < ActionMailer::Base
  default from: 'noreply@company.com'

  def instructions(user)
    @name = user.name
    @confirmation_url = confirmation_url(user)
    mail to: user.email, subject: 'Instructions'
  end
end

class User
  def send_instructions
    Notifier.instructions(self).deliver
  end
end

And the following test configuration:

# config/environments/test.rb
AppName::Application.configure do
  config.action_mailer.delivery_method = :test
end

These specs should get you what you want:

# spec/models/user_spec.rb
require 'spec_helper'

describe User do
  let(:user) { User.make }

  it "sends an email" do
    expect { user.send_instructions }.to change { ActionMailer::Base.deliveries.count }.by(1)
  end
end

# spec/mailers/notifier_spec.rb
require 'spec_helper'

describe Notifier do
  describe 'instructions' do
    let(:user) { mock_model User, name: 'Lucas', email: 'lucas@email.com' }
    let(:mail) { Notifier.instructions(user) }

    it 'renders the subject' do
      expect(mail.subject).to eql('Instructions')
    end

    it 'renders the receiver email' do
      expect(mail.to).to eql([user.email])
    end

    it 'renders the sender email' do
      expect(mail.from).to eql(['noreply@company.com'])
    end

    it 'assigns @name' do
      expect(mail.body.encoded).to match(user.name)
    end

    it 'assigns @confirmation_url' do
      expect(mail.body.encoded).to match("http://aplication_url/#{user.id}/confirmation")
    end
  end
end

Props to Lucas Caton for the original blog post on this topic.

notapatch
  • 6,569
  • 6
  • 41
  • 45
CDub
  • 13,146
  • 4
  • 51
  • 68
  • 3
    But that would not catch any problems if you, let's say catch an exception from the User.send_instructions and send yourself an email with the exception. You just test if *any* e-mail is sent, not your specific one. – Phillipp Apr 09 '15 at 17:24
  • 4
    @Phillipp made a good point, and if you'd like to test specific mail(s), `ActionMailer::Base.deliveries` is an array of `Mail::Message` objects. Refer to [Mail::Message API](http://www.rubydoc.info/github/mikel/mail/Mail/Message). – janechii Feb 12 '16 at 01:01
  • 2
    For those who wonder why `mock_model` doesn't work: http://stackoverflow.com/a/24060582/2899410 – Aesthetic May 18 '17 at 02:52
  • For those who want to test `deliver_later`, check this post too: https://stackoverflow.com/a/42987726/11792577 – glinda93 Apr 30 '21 at 08:56