Benito Serna
Ruby, Rails, TDD...

How to test that an email was sent on a unit/micro test

There are different practices, that I know, to test that an email was sent in Rails…

  1. Check for the ActionMailer::Base.deliveries
  2. Spy your own sender objects
  3. Use doubles for your mailer objects

All of them have pros and cons. That is why I think that is good to understand all of them because it will help you decide which method is better case by case.

1. Check for the ActionMailer::Base.deliveries

It will be similar to what you will do on a feature/capybara spec.

You will check the ActionMailer::Base.deliveries to see if the email that you want is there.

RSpec.describe Event, "#create_invitation" do
  it "sends the invitation email" do
    event = create :event

    event.create_invitation(name: "Mary", email: "mary@example.com")

    mail = find_mail_to("mary@example.com")

    expect(mail.subject).to eq "You have been invited to #{event.name}"
  end

  def find_mail_to(email)
    ActionMailer::Base.deliveries.find { |mail|
      mail.to.include?(email)
    }
  end
end

2. Spy your own sender objects

Using rspec, you can expect that an object will receive the right method that will actually deliver the mail.

RSpec.describe Event, "#send_invitation_emails" do
  it "sends the email for all invitations" do
    event = create :event
    invitation1 = create :invitation, event: event
    invitation2 = create :invitation, event: event

    expect(invitation1).to receive(:send_email)
    expect(invitation2).to receive(:send_email)

    event.send_invitation_emails
  end
end

3. Use doubles for your mailer objects

Using rspec, you can allow your mailer to receive new with the right attributes and then return an object that will receive the method that actually delivers the mail.

RSpec.describe Invitation, "#send_email" do
  it "sends the new_invitation email" do
    invitation = build :invitation

    mail = double("Mail")

    allow(EventMailer).to receive(:new_invitation).with(invitation).and_return(mail)

    expect(mail).to receive(:deliver_later)

    invitation.send_email
  end
end

What method should you use?

As you can see not all tests will test exactly the same thing. And most of the time you won’t need all of them.

If you are struggling deciding which test to write, I recommend you to start with the feature/capybara spec.

Then if you fill that you need to test something at the unit/micro level, you can try this methods in order, but just moving to the next method only if you really feel that is necesary.

So, you can start with the first method, then if you need more confidence you can try the second, and finally the third method if you really think that is necessary.

Related articles