Forum: Ruby on Rails ActionMailer, unit testing and multipart mails

Announcement (2017-05-07): is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see and for other Rails- und Ruby-related community platforms.
0897a8b801a5fe0752c2c0c5dc15f134?d=identicon&s=25 Trejkaz (Guest)
on 2008-12-02 03:33
(Received via mailing list)
Hi all.

Is there a "correct" way of writing a unit test for a mailer which
sends attachments?

I tried using the @expected variable as provided in
ActionMailer::TestCase, but it led to various problems.

Here's what I'm attempting...

  def test_notification
    @expected.from         = ''           = ''
    @expected.subject      = ''
    @expected.content_type = 'multipart/mixed; boundary="something"'

    body_part =
    body_part.content_type = 'text/plain'
    body_part.body         = read_fixture('notification') << body_part      # <=== ERROR HERE

    attach_part =
    attach_part.content_type = 'application/octet-stream'
    attach_part.encoding     = 'base64'
    attach_part.body         = 'abc' << attach_part

    mail = Notifier.create_notification()

    assert_equal @expected.encoded, mail.encoded

This gives the following error:

TypeError: can't convert nil into String
vendor/tmail-1.2.3/tmail/mail.rb:551:in `quote'
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:551:in `read_multipart'
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:540:in `parse_body_0'
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:526:in `parse_body'
action_mailer/vendor/tmail-1.2.3/tmail/stringio.rb:43:in `open'
action_mailer/vendor/tmail-1.2.3/tmail/port.rb:340:in `ropen'
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:524:in `parse_body'
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:497:in `parts'

Line 16 is commented in the above code.  Looking at mail.rb:551
suggests that body is nil, and I can confirm that the body passed in
was not nil.

I know I can just not use @expected and check each bit of the mail
separately, but I figure @expected is there for a reason (and also,
checking each bit of the mail only verifies what is supposed to be
there, not what isn't.)

Maybe I'm just doing things completely wrong, as I'm not 100% used to
Rails 2.x yet.

0897a8b801a5fe0752c2c0c5dc15f134?d=identicon&s=25 Trejkaz (Guest)
on 2008-12-12 01:06
(Received via mailing list)
So... nobody is doing mails with attachments?  Or anyone who is, isn't
unit testing their code?

Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2008-12-12 02:28
(Received via mailing list)
if you commented out this line (and any other line that squawks)...

> << body_part      # <=== ERROR HERE

and printed out the mail's parts before asserting them...

>     mail = Notifier.create_notification()


...what would you see?

We TDD e-mails all the time, but we don't write huge bulk assertions.
to test_first_, we cheat a little, write the correct code, print out
what it
does, and then write assertions which trap the important details.

To test-first, when you need to change a mail, you clone that test,
change the
input, change the assertions to expect different output, fail the test,
pass it in the code. This shows how cheating, especially in
situations like GUIs, can lead to better TDD...

0897a8b801a5fe0752c2c0c5dc15f134?d=identicon&s=25 Trejkaz (Guest)
on 2008-12-15 02:57
(Received via mailing list)
On Dec 12, 12:27 pm, Phlip <> wrote:
> ...what would you see?
It looks like this...

[#<TMail::Mail port=#<TMail::StringPort:id=0x2ea1e18>
 #<TMail::Mail port=#<TMail::StringPort:id=0x2ea1486>

Something I have just discovered today.  If you delay setting
content_type to *after* adding the parts, it gets rid of that
exception.  Now it appears to be at least not causing an error, but I
get a failure because (of course) the content types are subtly

What's truly bizarre about it is that the MIME boundary string is
quoted for the actual mail but not quoted for the expected mail, even
though it doesn't need to be quoted in either case.  TMail seems to be
doing some weird things there.

I think if I make the test environment overwrite TMail's new_boundary
method, it might give me a way to dodge that.

0897a8b801a5fe0752c2c0c5dc15f134?d=identicon&s=25 Trejkaz (Guest)
on 2008-12-15 03:32
(Received via mailing list)
Okay, solution found.  Requires a combination of two tricks:

1. content_type has to be set after the parts, as you get the error
above otherwise.  Maybe a bug in TMail, who knows.  At least there is
a workaround.

2. Boundary strings need mangling.  I created myself a convenience
method which will ultimately end up in my test helper if I have more
than one notification model later.

  def assert_mail_equal(expected_mail, actual_mail)
    assert_equal replace_boundary_strings(expected_mail.encoded),

  def replace_boundary_strings(str)
    boundary_pattern = 'mimepart_[0-9a-f]+_[0-9a-f]+'
    str = str.gsub(/(Content-Type: multipart\/mixed; boundary=)\"?#
{boundary_pattern}\"?/, "\\1replaced")
    str = str.gsub(/--#{boundary_pattern}/, "--replaced")

I had suspected there was a second solution in tricking TMail into
generating the same boundary string on calls to new_boundary, but I
couldn't get that to work.

This topic is locked and can not be replied to.