How to test with Rspec if an email was delivered?

Many times, we would like to validate if a class or a method sends an email. We don’t wanna worry too much about the specific details (i.e. body, subject, from, to) of the email but just wanna ensure that a certain mailer method is invoked with certain arguments.

This is how you can do it in Rspec:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
RSpec.describe UserWelcomeWorker, type: :worker do
  let(:message_delivery) { instance_double(ActionMailer::MessageDelivery)}

  before do
     allow(UserMailer).to receive(:send_welcome_email).and_return(message_delivery)
     allow(message_delivery).to receive(:deliver_now)
  end

  it 'sends welcome email to users' do
     described_class.new.perform(user.id)

     expect(UserMailer).to have_received(:send_welcome_email).with(user).once
  end
end

A Better Way: Writing Custom Matcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#spec/support/matchers/send_email_with.rb

RSpec::Matchers.define :send_email_with do |mailer, mailer_method, args = anything|
  match do |block|
    message_delivery = instance_double(ActionMailer::MessageDelivery)
    allow(mailer).to receive(mailer_method).and_return(message_delivery)
    allow(message_delivery).to receive(:deliver_now)
    block.call
    begin
      expect(mailer).to have_received(mailer_method).with(args).once
    rescue RSpec::Expectations::ExpectationNotMetError => e
      @error = e
      return false
    end
    true
  end

  supports_block_expectations

  failure_message do |t|
    @error
  end
end

# We can use this in our test like this:

it 'sends welcome email to users' do
  expect {
    described_class.new.perform(user)
  }.to send_email_with(UserMailer, :send_welcome_email, user)
end

What this does not cover?

  • If an email was scheduled to be sent using deliver_later
  • If an email was invoked using the with syntax like: UserMailer.with(user: user).send_welcome_email
  • This does not include validation of the actual contents of the email including subject, email, body, from, to email address. We use mailer specs for that.
  • This only shows an example in Rspec. We can follow the same approach for Minitest.
This post is licensed under CC BY 4.0 by the author.