gardelea-email_spec 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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