gardelea-email_spec 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ module EmailSpec::MailExt
2
+ def default_part
3
+ @default_part ||= html_part || text_part || self
4
+ end
5
+
6
+ def default_part_body
7
+ default_part.body
8
+ end
9
+ end
10
+
11
+ Mail::Message.send(:include, EmailSpec::MailExt)
@@ -0,0 +1,257 @@
1
+ module EmailSpec
2
+ module Matchers
3
+ class ReplyTo
4
+ def initialize(email)
5
+ @expected_reply_to = Mail::ReplyToField.new(email).addrs.first
6
+ end
7
+
8
+ def description
9
+ "have reply to as #{@expected_reply_to.address}"
10
+ end
11
+
12
+ def matches?(email)
13
+ @email = email
14
+ @actual_reply_to = (email.reply_to || []).first
15
+ !@actual_reply_to.nil? &&
16
+ @actual_reply_to == @expected_reply_to.address
17
+ end
18
+
19
+ def failure_message
20
+ "expected #{@email.inspect} to reply to #{@expected_reply_to.address.inspect}, but it replied to #{@actual_reply_to.inspect}"
21
+ end
22
+
23
+ def negative_failure_message
24
+ "expected #{@email.inspect} not to deliver to #{@expected_reply_to.address.inspect}, but it did"
25
+ end
26
+ end
27
+
28
+ def reply_to(email)
29
+ ReplyTo.new(email)
30
+ end
31
+
32
+ alias :have_reply_to :reply_to
33
+
34
+ class DeliverTo
35
+ def initialize(expected_email_addresses_or_objects_that_respond_to_email)
36
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
37
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
38
+ end
39
+
40
+ @expected_recipients = Mail::ToField.new(emails).addrs.map(&:to_s).sort
41
+ end
42
+
43
+ def description
44
+ "be delivered to #{@expected_recipients.inspect}"
45
+ end
46
+
47
+ def matches?(email)
48
+ @email = email
49
+ @actual_recipients = (email.header[:to].addrs || []).map(&:to_s).sort
50
+ @actual_recipients == @expected_recipients
51
+ end
52
+
53
+ def failure_message
54
+ "expected #{@email.inspect} to deliver to #{@expected_recipients.inspect}, but it delivered to #{@actual_recipients.inspect}"
55
+ end
56
+
57
+ def negative_failure_message
58
+ "expected #{@email.inspect} not to deliver to #{@expected_recipients.inspect}, but it did"
59
+ end
60
+ end
61
+
62
+ def deliver_to(*expected_email_addresses_or_objects_that_respond_to_email)
63
+ DeliverTo.new(expected_email_addresses_or_objects_that_respond_to_email.flatten)
64
+ end
65
+
66
+ alias :be_delivered_to :deliver_to
67
+
68
+ class DeliverFrom
69
+
70
+ def initialize(email)
71
+ @expected_sender = Mail::FromField.new(email).addrs.first
72
+ end
73
+
74
+ def description
75
+ "be delivered from #{@expected_sender}"
76
+ end
77
+
78
+ def matches?(email)
79
+ @email = email
80
+ @actual_sender = (email.header[:from].addrs || []).first
81
+
82
+ !@actual_sender.nil? &&
83
+ @actual_sender.to_s == @expected_sender.to_s
84
+ end
85
+
86
+ def failure_message
87
+ %(expected #{@email.inspect} to deliver from "#{@expected_sender.to_s}", but it delivered from "#{@actual_sender.to_s}")
88
+ end
89
+
90
+ def negative_failure_message
91
+ %(expected #{@email.inspect} not to deliver from "#{@expected_sender.to_s}", but it did)
92
+ end
93
+ end
94
+
95
+ def deliver_from(email)
96
+ DeliverFrom.new(email)
97
+ end
98
+
99
+ alias :be_delivered_from :deliver_from
100
+
101
+ class BccTo
102
+
103
+ def initialize(expected_email_addresses_or_objects_that_respond_to_email)
104
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
105
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
106
+ end
107
+
108
+ @expected_email_addresses = emails.sort
109
+ end
110
+
111
+ def description
112
+ "be bcc'd to #{@expected_email_addresses.inspect}"
113
+ end
114
+
115
+ def matches?(email)
116
+ @email = email
117
+ @actual_recipients = (Array(email.bcc) || []).sort
118
+ @actual_recipients == @expected_email_addresses
119
+ end
120
+
121
+ def failure_message
122
+ "expected #{@email.inspect} to bcc to #{@expected_email_addresses.inspect}, but it was bcc'd to #{@actual_recipients.inspect}"
123
+ end
124
+
125
+ def negative_failure_message
126
+ "expected #{@email.inspect} not to bcc to #{@expected_email_addresses.inspect}, but it did"
127
+ end
128
+ end
129
+
130
+ def bcc_to(*expected_email_addresses_or_objects_that_respond_to_email)
131
+ BccTo.new(expected_email_addresses_or_objects_that_respond_to_email.flatten)
132
+ end
133
+
134
+ class CcTo
135
+
136
+ def initialize(expected_email_addresses_or_objects_that_respond_to_email)
137
+ emails = expected_email_addresses_or_objects_that_respond_to_email.map do |email_or_object|
138
+ email_or_object.kind_of?(String) ? email_or_object : email_or_object.email
139
+ end
140
+
141
+ @expected_email_addresses = emails.sort
142
+ end
143
+
144
+ def description
145
+ "be cc'd to #{@expected_email_addresses.inspect}"
146
+ end
147
+
148
+ def matches?(email)
149
+ @email = email
150
+ @actual_recipients = (Array(email.cc) || []).sort
151
+ @actual_recipients == @expected_email_addresses
152
+ end
153
+
154
+ def failure_message
155
+ "expected #{@email.inspect} to cc to #{@expected_email_addresses.inspect}, but it was cc'd to #{@actual_recipients.inspect}"
156
+ end
157
+
158
+ def negative_failure_message
159
+ "expected #{@email.inspect} not to cc to #{@expected_email_addresses.inspect}, but it did"
160
+ end
161
+ end
162
+
163
+ def cc_to(*expected_email_addresses_or_objects_that_respond_to_email)
164
+ CcTo.new(expected_email_addresses_or_objects_that_respond_to_email.flatten)
165
+ end
166
+
167
+ RSpec::Matchers.define :have_subject do
168
+ match do |given|
169
+ given_subject = given.subject
170
+ expected_subject = expected.first
171
+
172
+ if expected_subject.is_a?(String)
173
+ description { "have subject of #{expected_subject.inspect}" }
174
+ failure_message_for_should { "expected the subject to be #{expected_subject.inspect} but was #{given_subject.inspect}" }
175
+ failure_message_for_should_not { "expected the subject not to be #{expected_subject.inspect} but was" }
176
+
177
+ given_subject == expected_subject
178
+ else
179
+ description { "have subject matching #{expected_subject.inspect}" }
180
+ failure_message_for_should { "expected the subject to match #{expected_subject.inspect}, but did not. Actual subject was: #{given_subject.inspect}" }
181
+ failure_message_for_should_not { "expected the subject not to match #{expected_subject.inspect} but #{given_subject.inspect} does match it." }
182
+
183
+ !!(given_subject =~ expected_subject)
184
+ end
185
+ end
186
+ end
187
+
188
+ RSpec::Matchers.define :include_email_with_subject do
189
+ match do |given_emails|
190
+ expected_subject = expected.first
191
+
192
+ if expected_subject.is_a?(String)
193
+ description { "include email with subject of #{expected_subject.inspect}" }
194
+ failure_message_for_should { "expected at least one email to have the subject #{expected_subject.inspect} but none did. Subjects were #{given_emails.map(&:subject).inspect}" }
195
+ failure_message_for_should_not { "expected no email with the subject #{expected_subject.inspect} but found at least one. Subjects were #{given_emails.map(&:subject).inspect}" }
196
+
197
+ given_emails.map(&:subject).include?(expected_subject)
198
+ else
199
+ description { "include email with subject matching #{expected_subject.inspect}" }
200
+ failure_message_for_should { "expected at least one email to have a subject matching #{expected_subject.inspect}, but none did. Subjects were #{given_emails.map(&:subject).inspect}" }
201
+ failure_message_for_should_not { "expected no email to have a subject matching #{expected_subject.inspect} but found at least one. Subjects were #{given_emails.map(&:subject).inspect}" }
202
+
203
+ !!(given_emails.any?{ |mail| mail.subject =~ expected_subject })
204
+ end
205
+ end
206
+ end
207
+
208
+ RSpec::Matchers.define :have_body_text do
209
+ match do |given|
210
+ expected_text = expected.first
211
+
212
+ if expected_text.is_a?(String)
213
+ normalized_body = given.default_part_body.to_s.gsub(/\s+/, " ")
214
+ normalized_expected = expected_text.gsub(/\s+/, " ")
215
+ description { "have body including #{normalized_expected.inspect}" }
216
+ failure_message_for_should { "expected the body to contain #{normalized_expected.inspect} but was #{normalized_body.inspect}" }
217
+ failure_message_for_should_not { "expected the body not to contain #{normalized_expected.inspect} but was #{normalized_body.inspect}" }
218
+
219
+ normalized_body.include?(normalized_expected)
220
+ else
221
+ given_body = given.default_part_body.to_s
222
+ description { "have body matching #{expected_text.inspect}" }
223
+ failure_message_for_should { "expected the body to match #{expected_text.inspect}, but did not. Actual body was: #{given_body.inspect}" }
224
+ failure_message_for_should_not { "expected the body not to match #{expected_text.inspect} but #{given_body.inspect} does match it." }
225
+
226
+ !!(given_body =~ expected_text)
227
+ end
228
+ end
229
+ end
230
+
231
+ def mail_headers_hash(email_headers)
232
+ email_headers.fields.inject({}) { |hash, field| hash[field.field.class::FIELD_NAME] = field.to_s; hash }
233
+ end
234
+
235
+ RSpec::Matchers.define :have_header do
236
+ match do |given|
237
+ given_header = given.header
238
+ expected_name, expected_value = *expected
239
+
240
+ if expected_value.is_a?(String)
241
+ description { "have header #{expected_name}: #{expected_value}" }
242
+
243
+ failure_message_for_should { "expected the headers to include '#{expected_name}: #{expected_value}' but they were #{mail_headers_hash(given_header).inspect}" }
244
+ failure_message_for_should_not { "expected the headers not to include '#{expected_name}: #{expected_value}' but they were #{mail_headers_hash(given_header).inspect}" }
245
+
246
+ given_header[expected_name].to_s == expected_value
247
+ else
248
+ description { "have header #{expected_name} with value matching #{expected_value.inspect}" }
249
+ failure_message_for_should { "expected the headers to include '#{expected_name}' with a value matching #{expected_value.inspect} but they were #{mail_headers_hash(given_header).inspect}" }
250
+ failure_message_for_should_not { "expected the headers not to include '#{expected_name}' with a value matching #{expected_value.inspect} but they were #{mail_headers_hash(given_header).inspect}" }
251
+
252
+ given_header[expected_name].to_s =~ expected_value
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,7 @@
1
+ module EmailSpec
2
+ class TestObserver
3
+ def self.delivered_email(message)
4
+ ActionMailer::Base.deliveries << message
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ Description:
2
+ Creates email_steps.rb in your cucumber step_definitions.
3
+
4
+ Examples:
5
+ `script/rails generate email_spec:steps`
@@ -0,0 +1,14 @@
1
+ # This generator adds email steps to the step definitions directory
2
+ require 'rails/generators'
3
+
4
+ module EmailSpec
5
+ class StepsGenerator < Rails::Generators::Base
6
+ def generate
7
+ copy_file 'email_steps.rb', 'features/step_definitions/email_steps.rb'
8
+ end
9
+
10
+ def self.source_root
11
+ File.join(File.dirname(__FILE__), 'templates')
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,206 @@
1
+ # Commonly used email steps
2
+ #
3
+ # To add your own steps make a custom_email_steps.rb
4
+ # The provided methods are:
5
+ #
6
+ # last_email_address
7
+ # reset_mailer
8
+ # open_last_email
9
+ # visit_in_email
10
+ # unread_emails_for
11
+ # mailbox_for
12
+ # current_email
13
+ # open_email
14
+ # read_emails_for
15
+ # find_email
16
+ #
17
+ # General form for email scenarios are:
18
+ # - clear the email queue (done automatically by email_spec)
19
+ # - execute steps that sends an email
20
+ # - check the user received an/no/[0-9] emails
21
+ # - open the email
22
+ # - inspect the email contents
23
+ # - interact with the email (e.g. click links)
24
+ #
25
+ # The Cucumber steps below are setup in this order.
26
+
27
+ module EmailHelpers
28
+ def current_email_address
29
+ # Replace with your a way to find your current email. e.g @current_user.email
30
+ # last_email_address will return the last email address used by email spec to find an email.
31
+ # Note that last_email_address will be reset after each Scenario.
32
+ last_email_address || "example@example.com"
33
+ end
34
+ end
35
+
36
+ World(EmailHelpers)
37
+
38
+ #
39
+ # Reset the e-mail queue within a scenario.
40
+ # This is done automatically before each scenario.
41
+ #
42
+
43
+ Given /^(?:a clear email queue|no emails have been sent)$/ do
44
+ reset_mailer
45
+ end
46
+
47
+ #
48
+ # Check how many emails have been sent/received
49
+ #
50
+
51
+ Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails?$/ do |address, amount|
52
+ unread_emails_for(address).size.should == parse_email_count(amount)
53
+ end
54
+
55
+ Then /^(?:I|they|"([^"]*?)") should have (an|no|\d+) emails?$/ do |address, amount|
56
+ mailbox_for(address).size.should == parse_email_count(amount)
57
+ end
58
+
59
+ Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails? with subject "([^"]*?)"$/ do |address, amount, subject|
60
+ unread_emails_for(address).select { |m| m.subject =~ Regexp.new(Regexp.escape(subject)) }.size.should == parse_email_count(amount)
61
+ end
62
+
63
+ Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails? with subject \/([^"]*?)\/$/ do |address, amount, subject|
64
+ unread_emails_for(address).select { |m| m.subject =~ Regexp.new(subject) }.size.should == parse_email_count(amount)
65
+ end
66
+
67
+ Then /^(?:I|they|"([^"]*?)") should receive an email with the following body:$/ do |address, expected_body|
68
+ open_email(address, :with_text => expected_body)
69
+ end
70
+
71
+ #
72
+ # Accessing emails
73
+ #
74
+
75
+ # Opens the most recently received email
76
+ When /^(?:I|they|"([^"]*?)") opens? the email$/ do |address|
77
+ open_email(address)
78
+ end
79
+
80
+ When /^(?:I|they|"([^"]*?)") opens? the email with subject "([^"]*?)"$/ do |address, subject|
81
+ open_email(address, :with_subject => subject)
82
+ end
83
+
84
+ When /^(?:I|they|"([^"]*?)") opens? the email with subject \/([^"]*?)\/$/ do |address, subject|
85
+ open_email(address, :with_subject => Regexp.new(subject))
86
+ end
87
+
88
+ When /^(?:I|they|"([^"]*?)") opens? the email with text "([^"]*?)"$/ do |address, text|
89
+ open_email(address, :with_text => text)
90
+ end
91
+
92
+ When /^(?:I|they|"([^"]*?)") opens? the email with text \/([^"]*?)\/$/ do |address, text|
93
+ open_email(address, :with_text => Regexp.new(text))
94
+ end
95
+
96
+ #
97
+ # Inspect the Email Contents
98
+ #
99
+
100
+ Then /^(?:I|they) should see "([^"]*?)" in the email subject$/ do |text|
101
+ current_email.should have_subject(text)
102
+ end
103
+
104
+ Then /^(?:I|they) should see \/([^"]*?)\/ in the email subject$/ do |text|
105
+ current_email.should have_subject(Regexp.new(text))
106
+ end
107
+
108
+ Then /^(?:I|they) should see "([^"]*?)" in the email body$/ do |text|
109
+ current_email.default_part_body.to_s.should include(text)
110
+ end
111
+
112
+ Then /^(?:I|they) should see \/([^"]*?)\/ in the email body$/ do |text|
113
+ current_email.default_part_body.to_s.should =~ Regexp.new(text)
114
+ end
115
+
116
+ Then /^(?:I|they) should see the email delivered from "([^"]*?)"$/ do |text|
117
+ current_email.should be_delivered_from(text)
118
+ end
119
+
120
+ Then /^(?:I|they) should see "([^\"]*)" in the email "([^"]*?)" header$/ do |text, name|
121
+ current_email.should have_header(name, text)
122
+ end
123
+
124
+ Then /^(?:I|they) should see \/([^\"]*)\/ in the email "([^"]*?)" header$/ do |text, name|
125
+ current_email.should have_header(name, Regexp.new(text))
126
+ end
127
+
128
+ Then /^I should see it is a multi\-part email$/ do
129
+ current_email.should be_multipart
130
+ end
131
+
132
+ Then /^(?:I|they) should see "([^"]*?)" in the email html part body$/ do |text|
133
+ current_email.html_part.body.to_s.should include(text)
134
+ end
135
+
136
+ Then /^(?:I|they) should see "([^"]*?)" in the email text part body$/ do |text|
137
+ current_email.text_part.body.to_s.should include(text)
138
+ end
139
+
140
+ #
141
+ # Inspect the Email Attachments
142
+ #
143
+
144
+ Then /^(?:I|they) should see (an|no|\d+) attachments? with the email$/ do |amount|
145
+ current_email_attachments.size.should == parse_email_count(amount)
146
+ end
147
+
148
+ Then /^there should be (an|no|\d+) attachments? named "([^"]*?)"$/ do |amount, filename|
149
+ current_email_attachments.select { |a| a.filename == filename }.size.should == parse_email_count(amount)
150
+ end
151
+
152
+ Then /^attachment (\d+) should be named "([^"]*?)"$/ do |index, filename|
153
+ current_email_attachments[(index.to_i - 1)].filename.should == filename
154
+ end
155
+
156
+ Then /^there should be (an|no|\d+) attachments? of type "([^"]*?)"$/ do |amount, content_type|
157
+ current_email_attachments.select { |a| a.content_type.include?(content_type) }.size.should == parse_email_count(amount)
158
+ end
159
+
160
+ Then /^attachment (\d+) should be of type "([^"]*?)"$/ do |index, content_type|
161
+ current_email_attachments[(index.to_i - 1)].content_type.should include(content_type)
162
+ end
163
+
164
+ Then /^all attachments should not be blank$/ do
165
+ current_email_attachments.each do |attachment|
166
+ attachment.read.size.should_not == 0
167
+ end
168
+ end
169
+
170
+ Then /^show me a list of email attachments$/ do
171
+ EmailSpec::EmailViewer::save_and_open_email_attachments_list(current_email)
172
+ end
173
+
174
+ #
175
+ # Interact with Email Contents
176
+ #
177
+
178
+ When /^(?:I|they) follow "([^"]*?)" in the email$/ do |link|
179
+ visit_in_email(link)
180
+ end
181
+
182
+ When /^(?:I|they) click the first link in the email$/ do
183
+ click_first_link_in_email
184
+ end
185
+
186
+ #
187
+ # Debugging
188
+ # These only work with Rails and OSx ATM since EmailViewer uses RAILS_ROOT and OSx's 'open' command.
189
+ # Patches accepted. ;)
190
+ #
191
+
192
+ Then /^save and open current email$/ do
193
+ EmailSpec::EmailViewer::save_and_open_email(current_email)
194
+ end
195
+
196
+ Then /^save and open all text emails$/ do
197
+ EmailSpec::EmailViewer::save_and_open_all_text_emails
198
+ end
199
+
200
+ Then /^save and open all html emails$/ do
201
+ EmailSpec::EmailViewer::save_and_open_all_html_emails
202
+ end
203
+
204
+ Then /^save and open all raw emails$/ do
205
+ EmailSpec::EmailViewer::save_and_open_all_raw_emails
206
+ end