/ Tags: RAILS-CODE / Categories: SOLUTIONS

Test Action Mailer Content With Rspec Matchers In Rails

Mailers break quietly — a renamed method in a template, a missing interpolation, a recipient list that silently resolves to nil. None of it surfaces until a customer says they never got the email.

Description

Test the mailer directly rather than through a request. MyMailer.welcome(user) returns a Mail::Message you can assert on without delivering anything, and it runs in milliseconds. Assert on the parts that matter: recipient, subject, and the specific content the template is responsible for producing. Both parts of a multipart email should be checked — HTML and text drift apart constantly because only one gets updated. For the enqueue side, use have_enqueued_mail in the spec for the code that triggers it. That keeps the mailer spec about content and the caller’s spec about behaviour.

Sample input:

  OrderMailer.confirmation(order)


Sample Output:

  # mail.to      => ["[email protected]"]
  # mail.subject => "Your order ABC-1 is confirmed"

Answer

  RSpec.describe OrderMailer, type: :mailer do
    let(:user)  { create(:user, email: "[email protected]", name: "Ada") }
    let(:order) { create(:order, user: user, sku: "ABC-1", total_cents: 4250) }

    describe "#confirmation" do
      subject(:mail) { described_class.confirmation(order) }

      it "addresses the order's user" do
        expect(mail.to).to eq(["[email protected]"])
        expect(mail.from).to eq(["[email protected]"])
      end

      it "names the SKU in the subject" do
        expect(mail.subject).to eq("Your order ABC-1 is confirmed")
      end

      it "includes the total in both parts" do
        html = mail.html_part.body.decoded
        text = mail.text_part.body.decoded

        expect(html).to include("42.50").and include("Ada")
        expect(text).to include("42.50")
      end

      it "links to the order" do
        expect(mail.html_part.body.decoded)
          .to include(order_url(order, host: "example.com"))
      end
    end
  end

  # The enqueue side, tested where the behaviour lives
  RSpec.describe CreateOrder do
    it "enqueues the confirmation email" do
      expect { described_class.new(user:, params:).call }
        .to have_enqueued_mail(OrderMailer, :confirmation)
    end
  end

Learn More

cdrrazan

Rajan Bhattarai

Full Stack Software Developer! 💻 🏡 Grad. Student, MCS. 🎓 Class of '23. GitKraken Ambassador 🇳🇵 2021/22. Works with Ruby / Rails. Photography when no coding. Also tweets a lot at TW / @cdrrazan!

Read More