Cucumber - Testing ActionMailer


#1

I need some help with this. I have installed email-spec (courtesy of
BMabey) and I have configure things thusly:

Scenario: E-Mail Exchange Rates to notify parties
Given a currency exchange feed from the Bank of Canada
When the currency exchange retrieval script runs
Then I should receive an email

Then /should receive (an|\d+) e-?mails?/ do |amount|
amount = 1 if amount == “an”
unread_emails_for(current_email_address).size.should == amount.to_i
end

…/environments/test.rb

Tell ActionMailer not to deliver emails to the real world.

The :test delivery method accumulates sent emails in the

ActionMailer::Base.deliveries array.

config.action_mailer.delivery_method = :test
config.gem ‘bmabey-email_spec’, :lib => ‘email_spec’

class MailerPublic < ActionMailer::Base
def forex_daily_notice(address,rates)
rates_effective = Date.today.to_s(:db)
recipients address.to_a
from “removed_email_address@domain.invalid”
reply_to “removed_email_address@domain.invalid”
subject “#{rates_effective} - Foreign Exchange Rates Notice”
body :rates_effective => rates_effective, :rates => rates
end
end

./views/mailer_public/forex_daily_notice.text.plain.erb
Canada Customs foreign currency exchange rates for
currency conversion of shipments made on <%=@rates_effective%>

When I run this

./bin/script.rb

Format and email the results.

if fx_hash_array
puts “got an array”
send_to = ‘removed_email_address@domain.invalid’
MailerPublic.deliver_forex_daily_notice!(send_to,fx_hash_array)
puts ActionMailer::Base.deliveries.length
ActionMailer::Base.deliveries.each { |m| puts m }
end

Then I see this:

got an array
0

This is my first attempt at using ActionMailer so no doubt I have
overlooked something basic. Can anyone tell me what it is?


#2

James B. wrote:

unread_emails_for(current_email_address).size.should == amount.to_i

if fx_hash_array
0

This is my first attempt at using ActionMailer so no doubt I have
overlooked something basic. Can anyone tell me what it is?

Looks right to me. To be honest though I have not used ActionMailer
outside of Rails (where it just works out of the box). I don’t see
anything wrong with the approach you are taking. Maybe the rails
mailing list could provide better advice. Sorry, I couldn’t be of any
help.

-Ben


#3

Ben M. wrote:

Sorry, I couldn’t be of any help.

Actually, you were a great help, but on a different thread. The reason
I was not seeing anything is that the default environment is
“development” and I have things configured so that the deliveries array
is only used in “test”.

Adding “puts” in front of the call to %x() in the step definition file
showed that the email is generated as expected. I have something else
wrong with my test, probably the address that I am checking for,


#4

I have reached this point in testing using email-spec:

The code:

Format and email the results.

if fx_hash_array
send_to = ‘removed_email_address@domain.invalid’
MailerPublic.deliver_forex_daily_notice!(send_to,fx_hash_array)
ActionMailer::Base.deliveries.each { |m| puts m } unless
Rails.env.production?
end

The Feature Scenario:

Scenario: E-Mail Exchange Rates to notify parties
Given a currency exchange feed from the Bank of Canada
And an empty email queue
When the currency exchange retrieval script runs
Then I should receive an email

The Steps:

Then /currency exchange retrieval script runs/ do
stdout = %x(#{RAILS_ROOT}/bin/hll_forex_ca_feed.rb #{@xchg_source})
puts stdout
puts current_email_address
end

… local_email_steps

Then /should receive (an|\d+) e-?mails?/ do |amount|
amount = 1 if amount == “an”
unread_emails_for(current_email_address).size.should == amount.to_i
end

The output:

Scenario: E-Mail Exchange Rates to notify parties
# features/app/models/currency_exchange_rates/
currency_exchange_rates.feature:19

xml source is:
/home/byrnejb/Software/Development/Projects/proforma.git/doc/data/fx-noon-all-2009-04-15.xml

Given a currency exchange feed from the Bank of Canada
  # features/app/models/currency_exchange_rates/step_definitions/
      currency_exchange_rates_steps.rb:5

And an empty email queue
  # features/step_definitions/local_email_steps.rb:28

From: removed_email_address@domain.invalid
Reply-To: removed_email_address@domain.invalid
To: removed_email_address@domain.invalid
Subject: [ForEx] 2009-04-28 - Foreign Exchange Rates Notice
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary=mimepart_49f713ebd7061_2d4d155c1a4737c82cd

–mimepart_49f713ebd7061_2d4d155c1a4737c82cd
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: Quoted-printable
Content-Disposition: inline

Canada Customs foreign currency exchange rates for 2009-04-28:

–mimepart_49f713ebd7061_2d4d155c1a4737c82cd–

removed_email_address@domain.invalid

When the currency exchange retrieval script runs
  # features/app/models/currency_exchange_rates/step_definitions/
      currency_exchange_rates_steps.rb:11

Then I should receive an email
  # features/step_definitions/local_email_steps.rb:49
  expected: 1,
       got: 0 (using ==)
  Diff:
  @@ -1,2 +1,2 @@
  -1
  +0
   (Spec::Expectations::ExpectationNotMetError)
  ./features/step_definitions/local_email_steps.rb:51:in
     `/should receive (an|\d+) e-?mails?/'
  features/app/models/currency_exchange_rates/currency_exchange_rates.feature:23:in 

`Then I should receive an email’

The email To: header value and the current_email_address are the same
insofar as I can tell. But the email_spec matcher is not finding that
address in the deliveries array. Any ideas as to what I am missing?


#5

James B. wrote:

I have, up to now, simply accepted the magic of injecting test methods
into classes and using the results. Now I need to have explained how I
get this to magic work with stand alone scripts. How do I get
ActionMailer::Base.deliveries created by ActionMailer in the script to
remain available to cucumber/email-spec?

I have plans to make email-spec work with any SMTP mailer and thereby
making it more "black-box"y and allow for external processes to be
easily tested… However, that is not done yet. As it stands now you
have two options: a) have the test execution and script in the same
process or b) Use ARMailer[1] in your testing environment. ARMailer
places the messages in a database for queuing. So instead of checking
the in-memory array EmailSpec will check the queue on the database.
Since this is just for testing then you could use a lightweight sqlite3
db to store this.

-Ben

  1. http://github.com/seattlerb/ar_mailer/tree/master

#6

James B. wrote:

I have reached this point in testing using email-spec:

The email To: header value and the current_email_address are the same
insofar as I can tell. But the email_spec matcher is not finding that
address in the deliveries array. Any ideas as to what I am missing?

I have determined what the problem is. I just do not know how to fix it.

What is happening is that I am testing a standalone (outside of a Rails
instance) Ruby script that requires ActionMailer. I call this script
from inside the step definition using the %x alias for Kernel#`. When
this runs it picks up the test environment and directs email output into
ActionMailer::Base.deliveries. However, this array only exists during
the script’s existence and naturally disappears when he script
terminates. In consequence, the results are not available for testing.

I have, up to now, simply accepted the magic of injecting test methods
into classes and using the results. Now I need to have explained how I
get this to magic work with stand alone scripts. How do I get
ActionMailer::Base.deliveries created by ActionMailer in the script to
remain available to cucumber/email-spec?


#7

What I ended up doing is dumping the deliveries array to a file from the
standalone script and reading it into the deliveries array inside the
step definitions file. A kludge, but it suffices.


#8

On Apr 29, 10:37 am, Ben M. removed_email_address@domain.invalid wrote:

However, that is not done yet. As it stands now you
have two options: a) have the test execution and script in the same
process or b) Use ARMailer[1] in your testing environment. ARMailer
places the messages in a database for queuing. So instead of checking
the in-memory array EmailSpec will check the queue on the database.
Since this is just for testing then you could use a lightweight sqlite3
db to store this.

For what it’s worth, I believe that ARMailer requires you to change
your base class for mailers from ActionMailer::Base to
ActionMailer::ARMailer, which makes it difficult to use ARMailer only
in your test environment. However, the adzap-ar_mailer[1] fork
supports using the ActionMailer::Base base class, which makes it easy
to use ARMailer only for testing (by specifying it in config/
environments/test.rb) while using another delivery mechanism for
development/production.

  1. http://github.com/adzap/ar_mailer/tree/master