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

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community 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.to           = ''
    @expected.subject      = ''
    @expected.content_type = 'multipart/mixed; boundary="something"'

    body_part = TMail::Mail.new
    body_part.content_type = 'text/plain'
    body_part.body         = read_fixture('notification')
    @expected.parts << body_part      # <=== ERROR HERE

    attach_part = TMail::Mail.new
    attach_part.content_type = 'application/octet-stream'
    attach_part.encoding     = 'base64'
    attach_part.body         = 'abc'
    @expected.parts << attach_part

    mail = Notifier.create_notification()

    assert_equal @expected.encoded, mail.encoded
  end

This gives the following error:

TypeError: can't convert nil into String
C:/ruby/lib/ruby/gems/1.8/gems/actionmailer-2.1.2/lib/action_mailer/
vendor/tmail-1.2.3/tmail/mail.rb:551:in `quote'
    C:/ruby/lib/ruby/gems/1.8/gems/actionmailer-2.1.2/lib/
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:551:in `read_multipart'
    C:/ruby/lib/ruby/gems/1.8/gems/actionmailer-2.1.2/lib/
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:540:in `parse_body_0'
    C:/ruby/lib/ruby/gems/1.8/gems/actionmailer-2.1.2/lib/
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:526:in `parse_body'
    C:/ruby/lib/ruby/gems/1.8/gems/actionmailer-2.1.2/lib/
action_mailer/vendor/tmail-1.2.3/tmail/stringio.rb:43:in `open'
    C:/ruby/lib/ruby/gems/1.8/gems/actionmailer-2.1.2/lib/
action_mailer/vendor/tmail-1.2.3/tmail/port.rb:340:in `ropen'
    C:/ruby/lib/ruby/gems/1.8/gems/actionmailer-2.1.2/lib/
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:524:in `parse_body'
    C:/ruby/lib/ruby/gems/1.8/gems/actionmailer-2.1.2/lib/
action_mailer/vendor/tmail-1.2.3/tmail/mail.rb:497:in `parts'
    C:/Projects/portal/trunk/test/unit/notifier_test.rb:16:in
`test_notification'

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.

TX
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?

TX
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)...

>     @expected.parts << body_part      # <=== ERROR HERE

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

>     mail = Notifier.create_notification()

    p mail.parts

...what would you see?

We TDD e-mails all the time, but we don't write huge bulk assertions.
Sometimes
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,
then
pass it in the code. This shows how cheating, especially in
high-bandwidth
situations like GUIs, can lead to better TDD...

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

[#<TMail::Mail port=#<TMail::StringPort:id=0x2ea1e18>
bodyport=#<TMail::StringPort:id=0x2ea1a12>>,
 #<TMail::Mail port=#<TMail::StringPort:id=0x2ea1486>
bodyport=#<TMail::StringPort:id=0x2ea0f68>>]

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
different.

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.

TX
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),
                 replace_boundary_strings(actual_mail.encoded)
  end

  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")
  end

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.

TX
This topic is locked and can not be replied to.