cmhobbs
cmhobbs

Reputation: 2499

Testing ActionMailer multipart emails(text and html version) with RSpec

I'm currently testing my mailers with RSpec, but I've started setting up multipart emails as described in the Rails Guides here: http://guides.rubyonrails.org/action_mailer_basics.html#sending-multipart-emails

I have both mailer templates in text and html formats, but it looks like my tests are only checking the HTML portion. Is there a way to check the text template separately?

Is it only checking the HTML view because it's first in the default order?

Upvotes: 33

Views: 11667

Answers (5)

Shiva
Shiva

Reputation: 12592

I have done this way, I found it simpler since the content of both emails is gonna be similar except styles and markup.

context 'When there are no devices' do
  it 'makes sure both HTML and text version emails are sent' do
    expect(mail.body.parts.count).to eq(2)
    # You can even make sure the types of the part are `html` and `text`
  end

  it 'does not list any lockboxes to be removed in both types emails' do
    mail.body.parts.each do |part|
      expect(part.body).to include('No devices to remove')
    end
  end
end

Upvotes: 1

Jens
Jens

Reputation: 1397

To make things even simpler, you can use

message.text_part    and
message.html_part

to find the respective parts. This works even for structured multipart/alternative messages with attachments. (Tested on Ruby 1.9.3 with Rails 3.0.14.)

These methods employ some kind of heuristic to find the respective message parts, so if your message has multiple text parts (e.g. as Apple Mail creates them) it might fail to do the "right thing".

This would change the above method to

def body_should_match_regex(mail, regex)
 if mail.multipart?
  ["text", "html"].each do |part|
   mail.send("#{part}_part").body.raw_source.should match(regex)
  end
 else
  mail.body.raw_source.should match(regex)
 end
end

which works for both plaintext (non-multipart) messages and multipart messages and tests all message bodies against a specific regular expression.

Now, any volunteers to make a "real" RSpec matcher out of this? :) Something like

@mail.bodies_should_match /foobar/

would be a lot nicer ...

Upvotes: 26

mjc
mjc

Reputation: 3426

If your email has attachments the text and html parts will end be placed in a multipart/alternative part. This is noted on under Sending Emails with Attachments in the Rails 3 Guide.

To handle this, I first simplified the get_message_part method above to:

def get_message_part(mail, content_type)
  mail.body.parts.find { |p| p.content_type.match content_type }
end

Then in my test:

multipart = get_message_part(email, /multipart/)

html = get_message_part(multipart, /html/)
html_body = html.body.raw_source

assert_match 'some string', html_body

Upvotes: 1

Lachlan Cotter
Lachlan Cotter

Reputation: 1889

To supplement, nilmethod's excellent answer, you can clean up your specs by testing both text and html versions using a shared example group:

spec_helper.rb

def get_message_part (mail, content_type)
  mail.body.parts.find { |p| p.content_type.match content_type }.body.raw_source
end

shared_examples_for "multipart email" do
  it "generates a multipart message (plain text and html)" do
    mail.body.parts.length.should eq(2)
    mail.body.parts.collect(&:content_type).should == ["text/plain; charset=UTF-8", "text/html; charset=UTF-8"]
  end
end

your_email_spec.rb

let(:mail) { YourMailer.action }

shared_examples_for "your email content" do
  it "has some content" do
    part.should include("the content")
  end
end

it_behaves_like "multipart email"

describe "text version" do
  it_behaves_like "your email content" do
    let(:part) { get_message_part(mail, /plain/) }
  end
end

describe "html version" do
  it_behaves_like "your email content" do
    let(:part) { get_message_part(mail, /html/) }
  end
end

Upvotes: 34

cmhobbs
cmhobbs

Reputation: 2499

This can be tested with regular expressions.

Finding things in the HTML portion (use #should after this to match):

mail.body.parts.find {|p| p.content_type.match /html/}.body.raw_source

Finding things in the plain text portion (use #should after this to match):

mail.body.parts.find {|p| p.content_type.match /plain/}.body.raw_source

Checking that it is, indeed, generating a multipart message:

it "generates a multipart message (plain text and html)" do
  mail.body.parts.length.should == 2
  mail.body.parts.collect(&:content_type).should == ["text/html; charset=UTF-8", "text/plain; charset=UTF-8"]
end 

Upvotes: 28

Related Questions