Specing raising error, handling, and then not raising error

Hey guys and gals,

I have a snippet of code:

Net::SMTP(@host, @port, @from_domain) do |smtp|
@emails.each do |email|
begin
smtp.send_message email.encoded, email.from, email.destinations
@emails_sent += 1
rescue Exception => e
# blah
end
end
end

What I want to do is:

Say there are 4 emails.

First email is sent OK
On the second email smtp raises a IOError error
The third and fourth emails are sent OK

I want to spec:

That the second iteration raises an error.
That the total emails sent count changed by 3

I can’t seem to figure out how to stub/mock the smtp object to return
an email on the 1, 3, and 4th iteration and raise an error on the 2nd
iteration.

I thought something like this, but it doesn’t work:

it “should only increase the sent emails counter if the email was
sent” do
@smtp = mock(Net::SMTP)
@smtp.should_receive(:send_message).exactly(4).times.and_return(true,
IOError, true, true)
Net::SMTP.stub!(:start).and_yield(@smtp)
@sender = Mailer::Sender.new(valid_args(:emails => @emails))
@sender.sent_emails.should == 3
end

If anyone has any pointers, that would be great.

I am using rSpec trunk - today’s release.

Mikel

On 4 Nov 2007, at 23:51, Mikel L. wrote:

The problem I was trying to solve with the mass sender is I only want
to open one connection to the SMTP server, not multiple.

I thought I covered that. I didn’t mock out the bit that wrapped
your existing code:

Net::SMTP(@host, @port, @from_domain) do |smtp|
# … previous implementation
end

So at some point you still need that one connection that gets passed
through the rest of the code. Class Spammer holds onto the SMTP but
it’s a transient object really - I pictured it being made in the
Net::SMTP block and discarded, eg:

Net::SMTP(@host, @port, @from_domain) do |smtp|
Spammer.new(smtp).send(@emails)
end

Does this help?

Ashley

blog @ http://aviewfromafar.net/
linked-in @ http://www.linkedin.com/in/ashleymoran
currently @ work

On Nov 04, 2007, at 9:55 am, Mikel L. wrote:

end
Mikel,

It looks like you are doing too much here. You are specifying sending
with the SMTP object in the same block you are specifying the
algorithm for counting the sent emails. Also, the way it interrogates
the email object to extract data looks fragile. I would prefer to
make the Email object responsible for sending itself, although I will
await the approval of an expert before I tell you my solution is better!

I had a go at this, and my solution is MUCH longer, but it breaks the
behaviour down to a finer level. (As you didn’t show the code for
Mailer::Sender, I wrote a quick implementation myself, possibly
similar to yours.) Also, I’m never quite sure how best to use
“before” blocks, so don’t take mine as a canonical example. Anyway
here is what I came up with:

class Email
def initialize(encoded_message, from, destination)
@encoded_message, @from, @destination =
encoded_message, from, destination
end

 def send_via(smtp)
   begin
     smtp.send_message(@encoded_message, @from, @destination)
   rescue IOError => e
     0
   else
     1
   end
 end

end

describe Email, :shared => true do
before(:each) do
@smtp = mock(“Net::SMTP”)
@email = Email.new(“message”, “from@mydomain”, “to@destination”)
end

 it "send_via: should send :send_message to the SMTP object with

the email details" do
@smtp.should_receive(:send_message).
with(“message”, “from@mydomain”, “to@destination”)
@email.send_via(@smtp)
end
end

describe Email, “sent via a working SMTP” do
it_should_behave_like “Email”

 before(:each) do
   @smtp.stub!(:send_message)
 end

 it "send_via: should return 1 if the email sent successfully" do
   @email.send_via(@smtp).should == 1
 end

end

describe Email, “sent via a faulty SMTP” do
it_should_behave_like “Email”

 before(:each) do
   @smtp.stub!(:send_message).and_raise(IOError.new)
 end

 it "send_via: should return 1 if the email sent successfully" do
   @email.send_via(@smtp).should == 0
 end

end

class Spammer
def initialize(smtp)
@smtp = smtp
end

 def send(emails)
   emails.inject(0) { |success_count, email| success_count +

email.send_via(@smtp) }
end
end

describe Spammer, “created with an SMTP” do
before(:each) do
@email_successful_1 = mock(Email)
@email_successful_1.stub!(:send_via).and_return(1)
@email_successful_2 = mock(Email)
@email_successful_2.stub!(:send_via).and_return(1)
@email_unsuccessful_1 = mock(Email)
@email_unsuccessful_1.stub!(:send_via).and_return(0)

   @emails = [ @email_successful_1, @email_successful_2,

@email_unsuccessful_1]

   @spammer = Spammer.new(:smtp)
 end

 it "send: should send each email in turn with the SMTP and return

the successful count" do

@email_successful_1.should_receive(:send_via).with(:smtp).and_return(1)

@email_successful_2.should_receive(:send_via).with(:smtp).and_return(1)

@email_unsuccessful_1
.should_receive(:send_via).with(:smtp).and_return(2)

   @spammer.send(@emails)
 end

 it "send: should return the count of successful emails" do
   @spammer.send(@emails).should == 2
 end

end

Let me know if this is helpful

Regards
Ashley


blog @ http://aviewfromafar.net/
linked-in @ http://www.linkedin.com/in/ashleymoran
currently @ home

On 11/5/07, Ashley M. [email protected] wrote:

Mikel,

It looks like you are doing too much here. You are specifying sending
with the SMTP object in the same block you are specifying the
algorithm for counting the sent emails. Also, the way it interrogates
the email object to extract data looks fragile. I would prefer to
make the Email object responsible for sending itself, although I will
await the approval of an expert before I tell you my solution is better!

HOLYCOMPLETESOLUTIONBATMAN!

:slight_smile:

Definately helpful… especially the bit on “trying to do too much”.

The problem I was trying to solve with the mass sender is I only want
to open one connection to the SMTP server, not multiple.

But your code definatley gave me some good ideas.

All this to get some email from one computer on the internet to
another that is not :slight_smile:

Anyway, speak soon, thanks again!

Mikel