Browse files

Refactored the way this spec helper approaches email. It stores state…

… internally a la webrat and has two perspectives for feature steps (user and "system").
  • Loading branch information...
1 parent 6cf35db commit 56b32c4660777fb1dc8008d84dc241b2c762bd7b @agibralter committed Dec 11, 2008
Showing with 101 additions and 41 deletions.
  1. +53 −7 lib/email_spec_helpers.rb
  2. +6 −6 lib/email_spec_matchers.rb
  3. +42 −28 lib/email_spec_steps.rb
View
60 lib/email_spec_helpers.rb
@@ -1,18 +1,64 @@
+require 'uri'
+
module EmailSpec
+
module Helpers
+
+ def self.extended(base)
+ base.instance_eval do
+ @email_spec_hash = {}
+ @email_spec_hash[:read_emails] = {}
+ @email_spec_hash[:unread_emails] = {}
+ @email_spec_hash[:current_emails] = {}
+ @email_spec_hash[:current_email] = nil
+ end
+ end
def reset_mailer
ActionMailer::Base.deliveries.clear
end
-
- def emails_sent_to(email)
- if email.is_a? Array
- email_array = email.sort
- ActionMailer::Base.deliveries.select { |m| m.to.sort == email_array }
+
+ def open_email(email_address, opts={})
+ if opts[:with_subject]
+ email = mailbox_for(email_address).find { |m| m.subject =~ Regexp.new(opts[:with_subject]) }
+ elsif opts[:with_text]
+ email = mailbox_for(email_address).find { |m| m.body =~ Regexp.new(opts[:with_text]) }
else
- ActionMailer::Base.deliveries.select { |m| m.to.include?(email) }
+ email = mailbox_for(email_address).first
end
+
+ read_emails_for(email_address) << email if email
+
+ @email_spec_hash[:current_emails][email_address] = email
+ @email_spec_hash[:current_email] = email
+ end
+
+ def current_email(email_address=nil)
+ email_address ? @email_spec_hash[:current_emails][email_address] : @email_spec_hash[:current_email]
end
+ def unread_emails_for(email_address)
+ mailbox_for(email_address) - read_emails_for(email_address)
+ end
+
+ def read_emails_for(email_address)
+ @email_spec_hash[:read_emails][email_address] ||= []
+ end
+
+ def mailbox_for(email)
+ ActionMailer::Base.deliveries.select { |m| m.to.include?(email) }
+ end
+
+ def parse_email_for_link(mail, link_text)
+ if mail.body.include?(link_text)
+ if link_text =~ %r{^/.*$}
+ # if it's an explicit link
+ link_text
+ elsif mail.body =~ %r{<a[^>]*href=['"]?([^'"]*)['"]?[^>]*?>[^<]*?#{link_text}[^<]*?</a>}
+ # if it's an anchor tag
+ URI.parse($~[1]).path
+ end
+ end
+ end
end
-end
+end
View
12 lib/email_spec_matchers.rb
@@ -1,7 +1,9 @@
module EmailSpec
+
module Matchers
class DeliverTo
+
def initialize(expected_email_addresses)
@expected_email_addresses = expected_email_addresses.sort
end
@@ -23,12 +25,10 @@ def negative_failure_message
end
def deliver_to(*expected_email_addresses_or_objects_that_respond_to_email)
- emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
- email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
- end
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
+ end
DeliverTo.new(emails)
end
-
-
end
-end
+end
View
70 lib/email_spec_steps.rb
@@ -1,37 +1,51 @@
-
-Given "no emails have been sent" do
+Given "a clear email queue" do
reset_mailer
end
-
-Then /^(\w+) should have received (\d+) emails*$/ do |actor, amount|
- email = actor =~ /@/ ? actor : instance_variable_get(actor).email
- emails_sent_to(email).size.should == amount.to_i
+
+# user perspective
+When %r{^'([^']*?)' opens (?:his)|(?:her) email with subject '([^']*?)'$} do |email, subject|
+ open_email(email, :with_subject => subject).should_not be_nil
end
-
-Then /^(\w+) should receive an email$/ do |actor|
- last_email_sent.to.should include(instance_variable_get(actor).email)
+
+When %r{^'([^']*?)' opens (?:his)|(?:her) email containing text '([^']*?)'$} do |email, text|
+ open_email(email, :with_text => text).should_not be_nil
end
-
-Then /^(\w+) should not receive an email$/ do |actor|
- #This is fragile because it depends on no previous steps sending a email to
- # the said actor before this step.. A Given clause could be used to clean up the mailer but
- # it would not look good in the stories..
- last_email_sent.to.should_not include(instance_variable_get(actor).email)
+
+When %r{^.*?follows '([^']*?)' in (?:his)|(?:her) email$} do |link_text|
+ current_email.should_not be_nil
+ link = parse_email_for_link(current_email, link_text)
+ get(link)
end
-Then /^an email should have been sent to (.+)$/ do |actor_or_email|
- email = actor_or_email["@"] ? actor_or_email : instance_variable_get(actor_or_email).email
- sent_email = ActionMailer::Base.deliveries.find{ |mail| mail.to.include? email }
- sent_email.should_not be_nil
- @current_email = sent_email
+When %r{^.*?clicks on '([^']*?)' in (?:his)|(?:her) email$} do |link_text|
+ current_email.should_not be_nil
+ link = parse_email_for_link(current_email, link_text)
+ get_via_redirect(link)
end
-
-Then /^an email should not have been sent to (.+)/ do |actor_or_email|
- email = actor_or_email["@"] ? actor_or_email : instance_variable_get(actor_or_email).email
- sent_email = ActionMailer::Base.deliveries.find{ |mail| mail.to.include? email }
- sent_email.should be_nil
+
+Then %r{^'([^']*?)' should have (\d+) new emails?$} do |email, n|
+ unread_emails_for(email).size.should == n.to_i
+end
+
+Then %r{^'([^']*?)' should have (\d+) total emails?$} do |email, n|
+ mailbox_for(email).size.should == n.to_i
+end
+
+# system perspective
+Then %r{^an email should have been sent to '([^']*?)'$} do |email|
+ open_email(email).should_not be_nil
end
-Then /^email should include text: "(.*)"$/ do |text|
- @current_email.body.should have_text(/#{text}/)
-end
+Then %r{^an email should not have been sent to '([^']*?)'$} do |email|
+ open_email(email).should be_nil
+end
+
+Then %r{^.*?email should have subject with text '([^']*?)'$} do |text|
+ current_email.should_not be_nil
+ current_email.subject.should =~ Regexp.new(text)
+end
+
+Then %r{^.*?email should include text '([^']*?)'$} do |text|
+ current_email.should_not be_nil
+ current_email.body.should =~ Regexp.new(text)
+end

3 comments on commit 56b32c4

@bmabey

Hey Aaron,
Overall it looks good. One suggestion is to change how open_email works. Everywhere you use it you say:
open_email(email, :with_text => text).should_not be_nil

It seems like you could do that nil check in the open_email method and provide a much more useful error message as well. How does that sound?

When I get some time I will pull your changes, thanks!

-Ben

@bmabey

Also, I don’t really like doing the instance eval in the extend hook.. That may be the best way ( I haven’t looked to closely), but my initial feeling is to extract that out into an accessor that lazily creates it. I’ll mess around with it later…

@agibralter
Owner

Ah good calls — yeah perhaps it should be open_email(email).should deliver_to(email)?

Please sign in to comment.