[rspec-users] Spec'ing ActionMailer

Craig Demyanovich cdemyanovich at gmail.com
Mon Feb 5 19:02:26 EST 2007

On Feb 5, 2007, at 2:45 PM, s.ross wrote:

> David/Craig--
> Two tests are actually what I'm doing. I'm testing two separate
> mailers. The first is triggered as a side effect of an action. That
> mailer is inextricably intertwined with the controller, as it has to
> provide a URL to the page of someone who signed up, etc. I suppose I
> could mock the controller object, but does that really provide better
> results?

Isolating the controller and mailer as much as possible is the best  
approach, IMHO. And, I don't think any mailer has to be bound tightly  
to a controller. Here's what I'd do.

Specify each mailer in isolation, focusing on all the interesting  
bits of the email messages that the mailers will create. For example:

context "An email for a new member" do
   specify "includes the URL used at sign up" do
     mail = MemberDigest.create_welcome(@member)
     mail.body.should_match /"#{@member.url}"/

Then I'd mock/stub the one mailer and verify that the controller uses  
it correctly. As a second option, I'd just let the controller use the  
mailer and verify that I had one delivery; I wouldn't pick apart the  
delivery, though, since I'd be comfortable that I'd specified the  
mailer well enough already.

> The second mailer is, as mentioned, triggered from the command line
> and is being tested in a fashion similar to AWDROR's. Here is the
> (acckkk!) code I settled on, which IMO violates a lot of OO  
> principles.
> require File.dirname(__FILE__) + '/../spec_helper.rb'
> context "A MemberDigest" do
>    fixtures :members
>    setup do
>      ActionMailer::Base.delivery_method = :test
>      ActionMailer::Base.perform_deliveries = true
>      ActionMailer::Base.deliveries = []
>      MemberDigest.trigger_emails   # invokes model method to send 4
> emails
>    end
>    specify "d'oh! should have a non-empty recipient list" do
>      ActionMailer::Base.deliveries.size.should be(4)
>      ActionMailer::Base.deliveries.inspect
>      ActionMailer::Base.deliveries.each do |email|
>        email.to_addrs.should_not be_empty
>      end
>    end
>    specify "first email should have a list item with the name of the
> first guy" do
>      ActionMailer::Base.deliveries.first.body.should_include(members
> (:existing_user_1).first)
>    end
>    teardown do
>      ActionMailer::Base.deliveries.clear
>    end
> end
> Comments?

Again, I'm tempted to specify the mailer in isolation and either  
trust that delivery will happen if I ask for it or specify delivery  
in isolation. Bear in mind that I might be missing something about  
this example, as my experience with Rails is still somewhat limited.

> On Feb 5, 2007, at 11:22 AM, Craig Demyanovich wrote:
>> On Feb 5, 2007, at 1:52 PM, s.ross wrote:
>>> David--
>>> I still don't have this working (see my previous email with the
>>> pastie attached), but now have another ActionMailer/rSpec question.
>>> I'm spec'ing a different mailer model directly (instead of through
>>> the controller). It's meant to be invoked through script/runner,  
>>> so I
>>> won't have a response object.
>> Steve,
>> Your comments got me thinking about this again. First, there is the
>> behavior that you expect your controller to exhibit: that certain
>> actions send email. Second, there is the behavior that your
>> ActionMailer::Base derivative should exhibit: that it creates email
>> as you like. Furthermore, you write that you want to invoke an
>> ActionMailer::Base derivative via script/runner instead of or in
>> addition to a controller.
>> Therefore, you should specify their respective behaviors
>> independently of one another. I'm including a code snippet from
>> _Agile Web Development with Rails, 2nd ed._, that shows an example of
>> testing the ActionMailer::Base derivative by itself. I'm sure you can
>> translate the test to a spec.
>> require File.dirname(__FILE__) + '/../test_helper'
>> require 'order_mailer'
>> class OrderMailerTest < Test::Unit::TestCase
>>    def setup
>>      @order = Order.new(:name =>"Dave Thomas", :email =>
>> "dave at pragprog.com")
>>    end
>>   def test_confirm
>>     response = OrderMailer.create_confirm(@order)
>>     assert_equal("Pragmatic Store Order Confirmation",
>> response.subject)
>>     assert_equal("dave at pragprog.com", response.to[0])
>>     assert_match(/Dear Dave Thomas/, response.body)
>>    end
>> end
>> Taking this approach will allow you to minimize the number of tests
>> that you write to prove that the controller behaves as it should.
>> Then, you can focus your attention on the ActionMailer::Base
>> derivative alone for all the things you want to verify about the
>> content of email. I hope this helps somehow.
>> Regards,
>> Craig
>> _______________________________________________
>> rspec-users mailing list
>> rspec-users at rubyforge.org
>> http://rubyforge.org/mailman/listinfo/rspec-users
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users

More information about the rspec-users mailing list