How to test Rails mailers using RSpec

How to test Rails mailers using RSpec

ActionMailer module has been reconstructed in Rails 3 and mailers have their own subdirectory (app/mailers) since then.

This blog post will demostrate how to test them in Rails using RSpec. Assuming that we have a mailer like the following:

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

To send an email through a method from User class:

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

Before test it, make sure the config/environments/test.rb file has the following configuration:

Rails.application.configure do
  config.action_mailer.delivery_method = :test
end

It ensures that emails won't be sent, but instead be stored on ActionMailer::Base.deliveries array.

So, in order to create the tests:

spec/models/user_spec.rb

require 'spec_helper'

RSpec.describe User, type: :model do
  subject { create :user }

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

spec/mailers/notifier_spec.rb

require 'spec_helper'

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

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

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

    it 'renders the sender email' do
      expect(mail.from).to eq(['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

Rails allows you to create very comprehensive tests for mailers. Happy coding!

  • Pingback: How to test mailers in Rails 3 with RSpec « Blog Vale | Blogosfera do Vale do Paraíba()

  • What about if the email sending occurs in the Users controller, as the guides (http://guides.rubyonrails.org/action_mailer_basics.html) use?

    class UsersController  'User was successfully created.') }
            format.xml  { render :xml => @user, :status => :created, :location => @user }
          else
            format.html { render :action => "new" }
            format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
          end
        end
      end
    end
    

  • Apologies about the poor formatting in my previous post. Consider putting some notice somewhere about using the pre tag or something.

  • You can call the ‘instructions’ method from anywhere, I just made a example :-)

  • Sorry about that, I already fix your code.

    But anyway, I’ll improve it.

  • That’s helpful. Thanks!

  • Arrghhh… can’t seem to find a way to access and test assigns directly. I’m trying *not* to test views.

  • @Wojtek Did you succeed in that? I have the same problem.

  • Rustam Gasanov

    Very good examples and explanation, Thank you!

  • Thanks Rustam!

  • #ensure that the subject is correct

    As a rule of thumb, code comments should describe why the code exists rather than what the code does. The code already says what it does so comments like the one above are redundant.

  • Rimian, you’re right!

    I don’t write my codes with that comments, it was written this way only to become more didactic.

  • Thanks for the simple straight forward example!

  • Pingback: From scratch: Rails Mailer, letter_opener, RSpec & Heroku | Gemfile()

  • Hugo Lepetit

    Hi there,

    you may want not to test the email you just sent like this in your user_spec.rb
    If ActionMailer::Base.deliveries does not reinit between each test, when you will add another test that uses @user. and that test runs before the one you wrote, you won’t be able to tell if your feature is broken.
    A solution would be to test if ActionMailer::Base.deliveries.count was changed.

  • lucascaton

    You’re right, I’ve just updated the post. Thanks dude.

  • Matt Campbell

    Lucas, is there a way to test which layout/template the mail instance has been rendered with?

  • lucascaton

    Hey @mecampbellsoup:disqus, I’m not sure but I reckon you can use `mail.body.raw_source` and check if the content you want is there?
    Hope this helps you!

  • Leighton James

    Thanks this is really useful and nice to follow.

  • lucascaton

    :-)

  • Frota passou por aqui

  • lucascaton

    Opa, volta sempre, obrigado pela visita ツ

  • Pingback: [KAP] Week 11. – TGIF : To GI every Friday()

Comments are closed.