Testing ActionMailer multipart emails(text and html version) with RSpec
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
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
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 ...