test_assistant 0.1.2 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +1 -164
- data/lib/test_assistant.rb +0 -2
- data/lib/test_assistant/configuration.rb +23 -61
- data/lib/test_assistant/version.rb +1 -1
- data/test_assistant.gemspec +0 -3
- metadata +2 -46
- data/lib/test_assistant/email/expectation.rb +0 -456
- data/lib/test_assistant/email/helpers.rb +0 -37
- data/lib/test_assistant/json/expectation.rb +0 -189
- data/lib/test_assistant/json/helpers.rb +0 -38
- data/spec/email_expectation_spec.rb +0 -369
- data/spec/eql_json_spec.rb +0 -430
- data/spec/support/email_mock.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8304109fa9e5a8dcd0e7664b4034699179311eb5
|
4
|
+
data.tar.gz: 847f3f376f40a1a4665e4a5866131488a2f04278
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1293ac55e9244756384126f45f2c8b54fca5cf6f9f55f9a001e8838661aa3fe224f479a5dee95fb838379950e43d28c32d3a121049eb5d8187babdd92d028d4
|
7
|
+
data.tar.gz: c3732fbe8aae17fa613a69d80328fe06063373f9917c02d83610c6cb90dbeaecea1df64ef390f28d31c40c1971ee014b49921659ebc4d174cbbb12cac5c84a94
|
data/README.md
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# TestAssistant
|
2
2
|
|
3
|
-
RSpec toolbox for writing and diagnosing Ruby on Rails tests, faster
|
3
|
+
RSpec toolbox for writing and diagnosing Ruby on Rails tests, faster.
|
4
4
|
|
5
5
|
## Features
|
6
6
|
|
7
7
|
* Light-weight, scoped, lazily executed and composable tool-box, so you only include the features you want to use, when you want to use them with no unnecessary overhead
|
8
|
-
* JSON assertion that gives noise-free reports on complex nested structures, so you can find out exactly what has changed with your JSON API without having to manually diff large objects
|
9
|
-
* Expressive email assertions that let you succinctly describe when emails should and should not sent
|
10
8
|
* Automatic reporting of the context around failing tests, so you don't have to re-run them with additional logging or a debugger
|
11
9
|
|
12
10
|
## Installation
|
@@ -35,155 +33,8 @@ RSpec.configure do |config|
|
|
35
33
|
end
|
36
34
|
```
|
37
35
|
|
38
|
-
## JSON expectations
|
39
|
-
|
40
|
-
Test Assistant lets you include helpers in your controller and request specs to get succinct declarative methods for defining the expected results for JSON responses.
|
41
|
-
|
42
|
-
### Setup
|
43
|
-
|
44
|
-
```ruby
|
45
|
-
TestAssistant.configure(config) do |ta_config|
|
46
|
-
ta_config.include_json_helpers type: :request
|
47
|
-
end
|
48
|
-
```
|
49
|
-
|
50
|
-
### Asserting JSON responses
|
51
|
-
|
52
|
-
Among the helpers provided are `json_response`, which automatically parses the last response object as json, and a custom assertion `eql_json` that reports failures in a format that is much clearer than anything provided by RSpec.
|
53
|
-
|
54
|
-
The full `expected` and `actual` values are still reported, but below is a separate report that only includes the paths to the failed nested values and their differences, removing the need to manually compare the two complete objects to find the difference.
|
55
|
-
|
56
|
-
```ruby
|
57
|
-
RSpec.describe 'making some valid request', type: :request do
|
58
|
-
context 'some important context' do
|
59
|
-
it 'should return some complicated JSON' do
|
60
|
-
|
61
|
-
perform_request
|
62
|
-
|
63
|
-
expect(json_response).to eql_json([
|
64
|
-
{
|
65
|
-
"a" => [
|
66
|
-
1, 2, 3
|
67
|
-
],
|
68
|
-
"c" => { "d" => "d'"}
|
69
|
-
},
|
70
|
-
{
|
71
|
-
"b" => [
|
72
|
-
1, 2, 3
|
73
|
-
],
|
74
|
-
"c" => { "d" => "d'"}
|
75
|
-
}
|
76
|
-
])
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
```
|
81
|
-
|
82
|
-
## Email expectations
|
83
|
-
|
84
|
-
Test Assistant provides a declarative API for describing when emails should be sent and their characteristics.
|
85
|
-
|
86
|
-
### Setup
|
87
|
-
|
88
|
-
```ruby
|
89
|
-
TestAssistant.configure(config) do |ta_config|
|
90
|
-
ta_config.include_email_helpers type: :request
|
91
|
-
end
|
92
|
-
```
|
93
|
-
|
94
|
-
### Clearing emails
|
95
|
-
|
96
|
-
Emails can be cleared at any point by calling `clear_emails` in your tests. This is helpful when you are testing a user workflow that may trigger multiple emails.
|
97
|
-
|
98
|
-
Emails are automatically cleared between each request spec.
|
99
|
-
|
100
|
-
### Email receiver address
|
101
|
-
|
102
|
-
It's possible to assert an email was sent to one or more or more addresses using the following format:
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
expect(email).to have_been_sent.to('user@email.com')
|
106
|
-
```
|
107
|
-
|
108
|
-
### Email sender address
|
109
|
-
|
110
|
-
Similarly, you can assert an email was sent from an address:
|
111
|
-
|
112
|
-
```ruby
|
113
|
-
expect(email).to have_been_sent.from('user@email.com')
|
114
|
-
```
|
115
|
-
|
116
|
-
### Email subject
|
117
|
-
|
118
|
-
You can assert an email's subject:
|
119
|
-
|
120
|
-
```ruby
|
121
|
-
expect(email).to have_been_sent.with_subject('Welcome!')
|
122
|
-
```
|
123
|
-
|
124
|
-
|
125
|
-
### Email body
|
126
|
-
|
127
|
-
You can assert the body of an email by text:
|
128
|
-
|
129
|
-
```ruby
|
130
|
-
expect(email).to have_been_sent.with_text('Welcome, user@email.com')
|
131
|
-
```
|
132
|
-
|
133
|
-
Or using a selector on the email's HTML:
|
134
|
-
|
135
|
-
```ruby
|
136
|
-
expect(email).to have_been_sent.with_selector('#password')
|
137
|
-
```
|
138
|
-
|
139
|
-
Or look for links:
|
140
|
-
|
141
|
-
```ruby
|
142
|
-
expect(email).to have_been_sent.with_link('www.site.com/onboarding/1')
|
143
|
-
```
|
144
|
-
|
145
|
-
Or images:
|
146
|
-
|
147
|
-
```ruby
|
148
|
-
expect(email).to have_been_sent.with_image('www.site.com/assets/images/welcome.png')
|
149
|
-
```
|
150
|
-
|
151
|
-
### Chaining assertions
|
152
|
-
|
153
|
-
You can chain any combination of the above that you want for ultra specific assertions:
|
154
|
-
|
155
|
-
|
156
|
-
```ruby
|
157
|
-
expect(email).to have_been_sent
|
158
|
-
.to('user@email.com')
|
159
|
-
.from('admin@site.com')
|
160
|
-
.with_subject('Welcome!')
|
161
|
-
.with_text('Welcome, user@email.com')
|
162
|
-
.with_selector('#password').and('#username')
|
163
|
-
.with_link('www.site.com/onboarding/1')
|
164
|
-
.with_image('www.site.com/assets/images/welcome.png')
|
165
|
-
|
166
|
-
```
|
167
|
-
|
168
|
-
You can also chain multiple assertions of the the same type with the `and` method:
|
169
|
-
|
170
|
-
```ruby
|
171
|
-
expect(email).to have_been_sent
|
172
|
-
.with_text('Welcome, user@email.com').and('Thanks for signing up')
|
173
|
-
```
|
174
|
-
|
175
|
-
### Asserting emails are NOT sent
|
176
|
-
|
177
|
-
The `have_sent_email` assertion works with the negative case as well:
|
178
|
-
|
179
|
-
```ruby
|
180
|
-
expect(email).to_not have_been_sent.with_text('Secret token')
|
181
|
-
```
|
182
|
-
|
183
36
|
## Failure Reporting
|
184
37
|
|
185
|
-
|
186
|
-
|
187
38
|
### Rendering a response context when a test fails
|
188
39
|
|
189
40
|
Test Assistant can automatically render the server response in your browser when a test fails and you have applied a nominated tag.
|
@@ -215,8 +66,6 @@ The `debug_failed_responses` accepts a the following options:
|
|
215
66
|
|
216
67
|
```ruby
|
217
68
|
TestAssistant.configure(config) do |ta_config|
|
218
|
-
ta_config.include_json_helpers type: :request
|
219
|
-
|
220
69
|
ta_config.debug_failed_responses tag: :debugger
|
221
70
|
end
|
222
71
|
```
|
@@ -231,14 +80,6 @@ RSpec.describe 'making some valid request', type: :request do
|
|
231
80
|
end
|
232
81
|
```
|
233
82
|
|
234
|
-
## Test suite
|
235
|
-
|
236
|
-
TestAssistant comes with close to complete test coverage. You can run the test suite as follows:
|
237
|
-
|
238
|
-
```bash
|
239
|
-
rspec
|
240
|
-
```
|
241
|
-
|
242
83
|
## Contributing
|
243
84
|
|
244
85
|
1. Fork it ( https://github.com/greena13/test_assistant/fork )
|
@@ -246,7 +87,3 @@ rspec
|
|
246
87
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
247
88
|
4. Push to the branch (`git push origin my-new-feature`)
|
248
89
|
5. Create a new Pull Request
|
249
|
-
|
250
|
-
## Inspirations
|
251
|
-
|
252
|
-
* [CapybaraEmail](https://github.com/DockYard/capybara-email)
|
data/lib/test_assistant.rb
CHANGED
@@ -4,8 +4,6 @@ require 'test_assistant/configuration'
|
|
4
4
|
# Utility module for working with RSpec test suites for Rails or similar applications
|
5
5
|
#
|
6
6
|
# Contains:
|
7
|
-
# - Expressive syntax for asserting testing emails
|
8
|
-
# - Sophisticated JSON matchers and diff failure reports
|
9
7
|
# - Rendering and debugging tools for viewing test failures
|
10
8
|
#
|
11
9
|
# @see https://github.com/greena13/test_assistant Test Assistant Github page
|
@@ -1,6 +1,4 @@
|
|
1
1
|
module TestAssistant
|
2
|
-
autoload :Json, 'test_assistant/json/helpers'
|
3
|
-
autoload :Email, 'test_assistant/email/helpers'
|
4
2
|
autoload :FailureReporter, 'test_assistant/failure_reporter'
|
5
3
|
|
6
4
|
# Class that provides configuration methods to control what parts of Test Assistant
|
@@ -19,49 +17,6 @@ module TestAssistant
|
|
19
17
|
@rspec_config = rspec_config
|
20
18
|
end
|
21
19
|
|
22
|
-
# Configures RSpec to include the JSON helpers provided by Test Assistant in the
|
23
|
-
# the test suite's scope
|
24
|
-
#
|
25
|
-
# @see TestAssistant::Json::Helpers
|
26
|
-
# @see RSpec::Core::Configuration#include
|
27
|
-
#
|
28
|
-
# @param [Hash] options RSpec::Core::Configuration#include options
|
29
|
-
# @return void
|
30
|
-
#
|
31
|
-
# @example Include JSON helpers in your RSpec test suite
|
32
|
-
# RSpec.configure do |config|
|
33
|
-
# TestAssistant.configure(config) do |ta_config|
|
34
|
-
# ta_config.include_json_helpers
|
35
|
-
# end
|
36
|
-
# end
|
37
|
-
def include_json_helpers(options = {})
|
38
|
-
@rspec_config.include Json::Helpers, options
|
39
|
-
end
|
40
|
-
|
41
|
-
# Configures RSpec to include the email helpers provided by Test Assistant in the
|
42
|
-
# the test suite's scope and to clear the list of emails sent after each test
|
43
|
-
#
|
44
|
-
# @see TestAssistant::Email::Helpers
|
45
|
-
# @see RSpec::Core::Configuration#include
|
46
|
-
#
|
47
|
-
# @param [Hash] options RSpec::Core::Configuration#include options
|
48
|
-
# @return void
|
49
|
-
#
|
50
|
-
# @example Include Email helpers in your RSpec test suite
|
51
|
-
# RSpec.configure do |config|
|
52
|
-
# TestAssistant.configure(config) do |ta_config|
|
53
|
-
# ta_config.include_email_helpers
|
54
|
-
# end
|
55
|
-
# end
|
56
|
-
def include_email_helpers(options = {})
|
57
|
-
@rspec_config.include Email::Helpers, options
|
58
|
-
|
59
|
-
@rspec_config.after :each, type: :request do
|
60
|
-
# clear emails after every request spec
|
61
|
-
ActionMailer::Base.deliveries = []
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
20
|
# Configures under what circumstances a failing test should open a failure report
|
66
21
|
# detailing the last HTTP request and response in a browser
|
67
22
|
#
|
@@ -105,12 +60,17 @@ module TestAssistant
|
|
105
60
|
no_type_filter = !type_filter
|
106
61
|
|
107
62
|
@rspec_config.after(:each) do |example|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
63
|
+
next unless example.exception
|
64
|
+
|
65
|
+
metadata = example.metadata
|
66
|
+
next unless (metadata[tag_filter] || no_tag_filter) && (metadata[:type] == type_filter || no_type_filter)
|
67
|
+
|
68
|
+
if metadata[:type] == :feature
|
69
|
+
save_and_open_page
|
70
|
+
else
|
71
|
+
reporter = FailureReporter.report(request, response)
|
72
|
+
reporter.write
|
73
|
+
reporter.open
|
114
74
|
end
|
115
75
|
end
|
116
76
|
end
|
@@ -153,16 +113,18 @@ module TestAssistant
|
|
153
113
|
no_type_filter = !type_filter
|
154
114
|
|
155
115
|
@rspec_config.after(:each) do |example|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
116
|
+
next unless example.exception
|
117
|
+
|
118
|
+
metadata = example.metadata
|
119
|
+
next unless (metadata.key?(tag_filter)) && (metadata[:type] == type_filter || no_type_filter)
|
120
|
+
|
121
|
+
# noinspection RubyResolve
|
122
|
+
if defined? binding
|
123
|
+
binding.pry
|
124
|
+
elsif defined? byebug
|
125
|
+
byebug
|
126
|
+
else
|
127
|
+
debugger
|
166
128
|
end
|
167
129
|
end
|
168
130
|
end
|
data/test_assistant.gemspec
CHANGED
@@ -18,9 +18,6 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency 'capybara', '~> 2.5', '>= 2.5.0'
|
22
|
-
spec.add_dependency 'hashdiff', '~> 0'
|
23
|
-
|
24
21
|
spec.add_development_dependency "bundler", "~> 1.6"
|
25
22
|
spec.add_development_dependency "rake", "~> 0"
|
26
23
|
spec.add_development_dependency "guard", "~> 2.1"
|
metadata
CHANGED
@@ -1,49 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: test_assistant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aleck Greenham
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: capybara
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '2.5'
|
20
|
-
- - ">="
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 2.5.0
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
requirements:
|
27
|
-
- - "~>"
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '2.5'
|
30
|
-
- - ">="
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: 2.5.0
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: hashdiff
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '0'
|
40
|
-
type: :runtime
|
41
|
-
prerelease: false
|
42
|
-
version_requirements: !ruby/object:Gem::Requirement
|
43
|
-
requirements:
|
44
|
-
- - "~>"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '0'
|
47
13
|
- !ruby/object:Gem::Dependency
|
48
14
|
name: bundler
|
49
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,16 +98,9 @@ files:
|
|
132
98
|
- Rakefile
|
133
99
|
- lib/test_assistant.rb
|
134
100
|
- lib/test_assistant/configuration.rb
|
135
|
-
- lib/test_assistant/email/expectation.rb
|
136
|
-
- lib/test_assistant/email/helpers.rb
|
137
101
|
- lib/test_assistant/failure_reporter.rb
|
138
|
-
- lib/test_assistant/json/expectation.rb
|
139
|
-
- lib/test_assistant/json/helpers.rb
|
140
102
|
- lib/test_assistant/version.rb
|
141
|
-
- spec/email_expectation_spec.rb
|
142
|
-
- spec/eql_json_spec.rb
|
143
103
|
- spec/spec_helper.rb
|
144
|
-
- spec/support/email_mock.rb
|
145
104
|
- test_assistant.gemspec
|
146
105
|
homepage: https://github.com/greena13/test_assistant
|
147
106
|
licenses:
|
@@ -168,7 +127,4 @@ signing_key:
|
|
168
127
|
specification_version: 4
|
169
128
|
summary: A toolbox for increased testing efficiency with RSpec
|
170
129
|
test_files:
|
171
|
-
- spec/email_expectation_spec.rb
|
172
|
-
- spec/eql_json_spec.rb
|
173
130
|
- spec/spec_helper.rb
|
174
|
-
- spec/support/email_mock.rb
|
@@ -1,456 +0,0 @@
|
|
1
|
-
require 'capybara/rspec'
|
2
|
-
|
3
|
-
module TestAssistant::Email
|
4
|
-
# Backing class for have_been_sent declarative syntax for specifying email
|
5
|
-
# expectations. Provides ability to assert emails have been sent in a given test
|
6
|
-
# that match particular attribute values, such as sender, receiver or contents.
|
7
|
-
#
|
8
|
-
# Expected to be used as part of a RSpec test suite and with
|
9
|
-
# TestAssistant::Email::Helpers#email.
|
10
|
-
#
|
11
|
-
# Has two major components:
|
12
|
-
# - A Builder or chainable methods syntax for constructing arbitrarily specific
|
13
|
-
# expectations
|
14
|
-
# - An implementation of the same interface as RSpec custom matcher classes to
|
15
|
-
# allow evaluating those expectations those expectations
|
16
|
-
#
|
17
|
-
# @see TestAssistant::Email::Helpers#email
|
18
|
-
# @see TestAssistant::Email::Helpers#have_been_sent
|
19
|
-
class Expectation
|
20
|
-
MATCHERS = {
|
21
|
-
to: :to,
|
22
|
-
from: :from,
|
23
|
-
with_subject: :subject,
|
24
|
-
with_text: {
|
25
|
-
match: ->(_, email, value){ value.all?{|text| email.has_content?(text) }},
|
26
|
-
actual: ->(_, email){ email.text}
|
27
|
-
},
|
28
|
-
matching_selector: {
|
29
|
-
match: ->(_, email, value){ value.all?{|selector| email.has_selector?(selector) }},
|
30
|
-
actual: ->(_, email){ email.native },
|
31
|
-
actual_name: :with_body
|
32
|
-
},
|
33
|
-
with_link: {
|
34
|
-
match: ->(_, email, value){ value.all?{|url| email.has_selector?("a[href='#{url}']") }},
|
35
|
-
actual: ->(_, email){ email.native },
|
36
|
-
actual_name: :with_body
|
37
|
-
},
|
38
|
-
with_image: {
|
39
|
-
match: ->(_, email, value){ value.all?{|url| email.has_selector?("img[src='#{url}']") }},
|
40
|
-
actual: ->(_, email){ email.native },
|
41
|
-
actual_name: :with_body
|
42
|
-
}
|
43
|
-
}
|
44
|
-
|
45
|
-
# Creates a new TestAssistant::Email::Expectation object
|
46
|
-
#
|
47
|
-
# @return [TestAssistant::Email::Expectation] new expectation object
|
48
|
-
def initialize
|
49
|
-
@expectations = {}
|
50
|
-
@failure_message = 'Expected email to be sent'
|
51
|
-
@and_scope = nil
|
52
|
-
end
|
53
|
-
|
54
|
-
#
|
55
|
-
# Expectation creation methods
|
56
|
-
#
|
57
|
-
|
58
|
-
# Allows chaining two assertions on the same email attribute together without having
|
59
|
-
# to repeat the same method. Intended as syntactical sugar only and is functionally
|
60
|
-
# equivalent to repeating the method.
|
61
|
-
#
|
62
|
-
# @example Asserting an email was sent to two email addresses
|
63
|
-
# expect(email).to have_been_sent.to('user1@email.com').and('user2@email.com')
|
64
|
-
#
|
65
|
-
# @param [Array] arguments parameters to pass to whatever assertion is being
|
66
|
-
# extended.
|
67
|
-
# @return [TestAssistant::Email::Expectation] reference to self, to allow for
|
68
|
-
# further method chaining
|
69
|
-
def and(*arguments)
|
70
|
-
if @and_scope
|
71
|
-
self.send(@and_scope, *arguments)
|
72
|
-
else
|
73
|
-
ArugmentError.new("Cannot use and without a proceeding assertion.")
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# For constructing an assertion that at least one email was sent to a particular
|
78
|
-
# email address
|
79
|
-
#
|
80
|
-
# @example Asserting an email was sent to user@email.com
|
81
|
-
# expect(email).to have_been_sent.to('user@email.com')
|
82
|
-
#
|
83
|
-
# @param [String, Array<String>] email_address address email is expected to be
|
84
|
-
# sent to. If an array of email addresses, the email is expected to have been
|
85
|
-
# sent to all of them.
|
86
|
-
# @return [TestAssistant::Email::Expectation] reference to self, to allow for
|
87
|
-
# further method chaining
|
88
|
-
def to(email_address)
|
89
|
-
@expectations[:to] ||= []
|
90
|
-
|
91
|
-
if email_address.kind_of?(Array)
|
92
|
-
@expectations[:to] = @expectations[:to].concat(email_address)
|
93
|
-
else
|
94
|
-
@expectations[:to] ||= []
|
95
|
-
@expectations[:to] << email_address
|
96
|
-
end
|
97
|
-
|
98
|
-
@and_scope = :to
|
99
|
-
|
100
|
-
self
|
101
|
-
end
|
102
|
-
|
103
|
-
# For constructing an assertion that at least one email was sent from a particular
|
104
|
-
# email address
|
105
|
-
#
|
106
|
-
# @example Asserting an email was sent from admin@site.com
|
107
|
-
# expect(email).to have_been_sent.from('admin@site.com')
|
108
|
-
#
|
109
|
-
# @param [String] email_address address email is expected to be sent from.
|
110
|
-
# @raise ArgumentError when from is called more than once on the same expectation,
|
111
|
-
# as an email can only ben sent from a single sender.
|
112
|
-
# @return [TestAssistant::Email::Expectation] reference to self, to allow for
|
113
|
-
# further method chaining
|
114
|
-
def from(email_address)
|
115
|
-
if @expectations[:from]
|
116
|
-
raise ArgumentError('An email can only have one from address, but you tried to assert the presence of 2 or more values')
|
117
|
-
else
|
118
|
-
@expectations[:from] = email_address
|
119
|
-
end
|
120
|
-
|
121
|
-
@and_scope = :from
|
122
|
-
|
123
|
-
self
|
124
|
-
end
|
125
|
-
|
126
|
-
# For constructing an assertion that at least one email was sent with a particular
|
127
|
-
# subject line
|
128
|
-
#
|
129
|
-
# @example Asserting an email was sent with subject line 'Hello'
|
130
|
-
# expect(email).to have_been_sent.with_subject('Hello')
|
131
|
-
#
|
132
|
-
# @param [String] subject Subject line an email is expected to have been sent with
|
133
|
-
# @raise ArgumentError when with_subject is called more than once on the same
|
134
|
-
# expectation, as an email can only have one subject line.
|
135
|
-
# @return [TestAssistant::Email::Expectation] reference to self, to allow for
|
136
|
-
# further method chaining
|
137
|
-
def with_subject(subject)
|
138
|
-
if @expectations[:with_subject]
|
139
|
-
raise ArgumentError('An email can only have one subject, but you tried to assert the presence of 2 or more values')
|
140
|
-
else
|
141
|
-
@expectations[:with_subject] = subject
|
142
|
-
end
|
143
|
-
|
144
|
-
@and_scope = :with_subject
|
145
|
-
|
146
|
-
self
|
147
|
-
end
|
148
|
-
|
149
|
-
# For constructing an assertion that at least one email was sent with a particular
|
150
|
-
# string in the body of the email
|
151
|
-
#
|
152
|
-
# @example Asserting an email was sent with the text 'User 1'
|
153
|
-
# expect(email).to have_been_sent.with_text('User 1')
|
154
|
-
#
|
155
|
-
# @param [String] text Text an email is expected to have been sent with in the body
|
156
|
-
# @return [TestAssistant::Email::Expectation] reference to self, to allow for
|
157
|
-
# further method chaining
|
158
|
-
def with_text(text)
|
159
|
-
@expectations[:with_text] ||= []
|
160
|
-
@expectations[:with_text].push(text)
|
161
|
-
|
162
|
-
@and_scope = :with_text
|
163
|
-
self
|
164
|
-
end
|
165
|
-
|
166
|
-
# For constructing an assertion that at least one email was sent with a body that
|
167
|
-
# matches a particular CSS selector
|
168
|
-
#
|
169
|
-
# @example Asserting an email was sent with a body matching selector '#imporant-div'
|
170
|
-
# expect(email).to have_been_sent.matching_selector('#imporant-div')
|
171
|
-
#
|
172
|
-
# @param [String] selector CSS selector that should match at least one sent
|
173
|
-
# email's body
|
174
|
-
# @return [TestAssistant::Email::Expectation] reference to self, to allow for
|
175
|
-
# further method chaining
|
176
|
-
def matching_selector(selector)
|
177
|
-
@expectations[:matching_selector] ||= []
|
178
|
-
@expectations[:matching_selector].push(selector)
|
179
|
-
|
180
|
-
@and_scope = :matching_selector
|
181
|
-
self
|
182
|
-
end
|
183
|
-
|
184
|
-
# For constructing an assertion that at least one email was sent with a link to
|
185
|
-
# a particular url in the body
|
186
|
-
#
|
187
|
-
# @example Asserting an email was sent with a link to http://www.example.com
|
188
|
-
# expect(email).to have_been_sent.with_link('http://www.example.com')
|
189
|
-
#
|
190
|
-
# @param [String] href URL that should appear in at least one sent email's body
|
191
|
-
# @return [TestAssistant::Email::Expectation] reference to self, to allow for
|
192
|
-
# further method chaining
|
193
|
-
def with_link(href)
|
194
|
-
@expectations[:with_link] ||= []
|
195
|
-
@expectations[:with_link].push(href)
|
196
|
-
|
197
|
-
@and_scope = :with_link
|
198
|
-
self
|
199
|
-
end
|
200
|
-
|
201
|
-
# For constructing an assertion that at least one email was sent with an image
|
202
|
-
# hosted at a particular URL
|
203
|
-
#
|
204
|
-
# @example Asserting an email was sent with the image http://www.example.com/image.png
|
205
|
-
# expect(email).to have_been_sent.with_link('http://www.example.com/image.png')
|
206
|
-
#
|
207
|
-
# @param [String] src URL of the image that should appear in at least one sent
|
208
|
-
# email's body
|
209
|
-
# @return [TestAssistant::Email::Expectation] reference to self, to allow for
|
210
|
-
# further method chaining
|
211
|
-
def with_image(src)
|
212
|
-
@expectations[:with_image] ||= []
|
213
|
-
@expectations[:with_image].push(src)
|
214
|
-
|
215
|
-
@and_scope = :with_image
|
216
|
-
self
|
217
|
-
end
|
218
|
-
|
219
|
-
#
|
220
|
-
# RSpec Matcher methods
|
221
|
-
#
|
222
|
-
|
223
|
-
# Declares that RSpec should not attempt to diff the actual and expected values
|
224
|
-
# to put in the failure message. This class takes care of diffing and presenting
|
225
|
-
# the differences, itself.
|
226
|
-
# @return [false] always returns false
|
227
|
-
def diffable?
|
228
|
-
false
|
229
|
-
end
|
230
|
-
|
231
|
-
# Whether at least one email was sent during the current test that matches the
|
232
|
-
# constructed expectation
|
233
|
-
# @return [Boolean] whether a matching email was sent
|
234
|
-
def matches?(emails)
|
235
|
-
@emails = emails
|
236
|
-
|
237
|
-
matching_emails = @emails
|
238
|
-
|
239
|
-
if @expectations.any?
|
240
|
-
@expectations.each do |attribute, expected|
|
241
|
-
@failed_attribute = attribute
|
242
|
-
@failed_expected = expected
|
243
|
-
|
244
|
-
matching_emails =
|
245
|
-
matching_emails.select do |email|
|
246
|
-
email_matches?(email, MATCHERS[attribute], expected)
|
247
|
-
end
|
248
|
-
|
249
|
-
if matching_emails.empty?
|
250
|
-
return false
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
true
|
255
|
-
else
|
256
|
-
@emails.any?
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
# Message to display to StdOut by RSpec if the equality check fails. Includes a
|
261
|
-
# complete a human-readable summary of the differences between what emails were
|
262
|
-
# expected to be sent, and what were actually sent (if any). Only used when the
|
263
|
-
# positive assertion is used, i.e. expect(email).to have_been_sent. For the
|
264
|
-
# failure message used for negative assertions, i.e.
|
265
|
-
# expect(email).to_not have_been_sent, see #failure_message_when_negated
|
266
|
-
#
|
267
|
-
# @see #failure_message_when_negated
|
268
|
-
#
|
269
|
-
# @return [String] message full failure message with explanation of the differences
|
270
|
-
# between what emails were expected and what was actually sent
|
271
|
-
def failure_message
|
272
|
-
field_descs = attribute_descriptions
|
273
|
-
value_descs = value_descriptions
|
274
|
-
|
275
|
-
base_clause = expectation_description(
|
276
|
-
'Expected an email to be sent',
|
277
|
-
field_descs,
|
278
|
-
value_descs
|
279
|
-
)
|
280
|
-
|
281
|
-
if @emails.length == 0
|
282
|
-
"#{base_clause} However, no emails were sent."
|
283
|
-
else
|
284
|
-
email_values = sent_email_values
|
285
|
-
|
286
|
-
if email_values.any?
|
287
|
-
base_clause + " However, #{email_pluralisation(@emails)} sent #{result_description(field_descs, [to_sentence(email_values)])}."
|
288
|
-
else
|
289
|
-
base_clause
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
# Failure message to display for negative RSpec assertions, i.e.
|
295
|
-
# expect(email).to_not have_been_sent. For the failure message displayed for positive
|
296
|
-
# assertions, see #failure_message.
|
297
|
-
#
|
298
|
-
# @see #failure_message
|
299
|
-
#
|
300
|
-
# @return [String] message full failure message with explanation of the differences
|
301
|
-
# between what emails were expected and what was actually sent
|
302
|
-
def failure_message_when_negated
|
303
|
-
field_descs = attribute_descriptions(negated: true)
|
304
|
-
value_descs = value_descriptions(negated: true)
|
305
|
-
|
306
|
-
expectation_description(
|
307
|
-
'Expected no emails to be sent',
|
308
|
-
field_descs,
|
309
|
-
value_descs
|
310
|
-
)
|
311
|
-
end
|
312
|
-
|
313
|
-
private
|
314
|
-
|
315
|
-
def result_description(field_descriptions, values)
|
316
|
-
to_sentence(
|
317
|
-
field_descriptions.map.with_index do |field_description, index|
|
318
|
-
value = values[index]
|
319
|
-
|
320
|
-
if [ 'matching selector', 'with link', 'with image' ].include?(field_description)
|
321
|
-
"with body #{value}"
|
322
|
-
else
|
323
|
-
"#{field_description} #{value}"
|
324
|
-
end
|
325
|
-
end
|
326
|
-
)
|
327
|
-
end
|
328
|
-
|
329
|
-
def sent_email_values
|
330
|
-
@emails.inject([]) do |memo, email|
|
331
|
-
|
332
|
-
if [ :matching_selector, :with_link, :with_image ].include?(@failed_attribute)
|
333
|
-
memo << email_body(email)
|
334
|
-
else
|
335
|
-
matcher = MATCHERS[@failed_attribute]
|
336
|
-
|
337
|
-
value =
|
338
|
-
case matcher
|
339
|
-
when String, Symbol
|
340
|
-
email.send(matcher)
|
341
|
-
when Hash
|
342
|
-
field_description = matcher[:actual_name] if matcher[:actual_name]
|
343
|
-
matcher[:actual].(email, parsed_emails(email))
|
344
|
-
end
|
345
|
-
|
346
|
-
value = value.kind_of?(String) ? "'#{value}'" : value.map{|element| "'#{element}'"}
|
347
|
-
memo << value
|
348
|
-
end
|
349
|
-
|
350
|
-
memo
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
def expectation_description(base_clause, field_descriptions, value_descriptions)
|
355
|
-
description = base_clause
|
356
|
-
|
357
|
-
additional_clauses = []
|
358
|
-
|
359
|
-
field_descriptions.each.with_index do |field_description, index|
|
360
|
-
clause = ''
|
361
|
-
clause += " #{field_description}" if field_description.length > 0
|
362
|
-
|
363
|
-
if (value_description = value_descriptions[index])
|
364
|
-
clause += " #{value_description}"
|
365
|
-
end
|
366
|
-
|
367
|
-
additional_clauses.push(clause) if clause.length > 0
|
368
|
-
end
|
369
|
-
|
370
|
-
description + additional_clauses.join('') + '.'
|
371
|
-
end
|
372
|
-
|
373
|
-
def attribute_descriptions(negated: false)
|
374
|
-
attributes_to_describe =
|
375
|
-
if negated
|
376
|
-
@expectations.keys
|
377
|
-
else
|
378
|
-
[ @failed_attribute ]
|
379
|
-
end
|
380
|
-
|
381
|
-
attributes_to_describe.map do |attribute|
|
382
|
-
attribute.to_s.gsub('_', ' ')
|
383
|
-
end
|
384
|
-
end
|
385
|
-
|
386
|
-
def value_descriptions(negated: false)
|
387
|
-
values_to_describe =
|
388
|
-
if negated
|
389
|
-
@expectations.values
|
390
|
-
else
|
391
|
-
[ @failed_expected ]
|
392
|
-
end
|
393
|
-
|
394
|
-
values_to_describe.map do |value|
|
395
|
-
case value
|
396
|
-
when String
|
397
|
-
"'#{value}'"
|
398
|
-
when Array
|
399
|
-
to_sentence(value.map{|val| "'#{val}'"})
|
400
|
-
else
|
401
|
-
value
|
402
|
-
end
|
403
|
-
end
|
404
|
-
|
405
|
-
end
|
406
|
-
|
407
|
-
def email_pluralisation(emails)
|
408
|
-
emails.length > 2 ? "#{emails.length} were": "1 was"
|
409
|
-
end
|
410
|
-
|
411
|
-
def to_sentence(items)
|
412
|
-
case items.length
|
413
|
-
when 0, 1
|
414
|
-
items.join('')
|
415
|
-
when 2
|
416
|
-
items.join(' and ')
|
417
|
-
else
|
418
|
-
items[0..(items.length-3)].join(', ') + items[(items.length-3)..items.length-1].join(' and ')
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
def parsed_emails(email)
|
423
|
-
@parsed_emails ||= {}
|
424
|
-
@parsed_emails[email] ||= parser(email)
|
425
|
-
@parsed_emails[email]
|
426
|
-
end
|
427
|
-
|
428
|
-
def parser(email)
|
429
|
-
Capybara::Node::Simple.new(email_body(email))
|
430
|
-
end
|
431
|
-
|
432
|
-
def email_body(email)
|
433
|
-
if email.parts.first
|
434
|
-
email.parts.first.body.decoded
|
435
|
-
else
|
436
|
-
email.body.encoded
|
437
|
-
end
|
438
|
-
end
|
439
|
-
|
440
|
-
def email_matches?(email, assertion, expected)
|
441
|
-
|
442
|
-
case assertion
|
443
|
-
when :to
|
444
|
-
(expected & email.send(assertion)).length > 0
|
445
|
-
when String, Symbol
|
446
|
-
email.send(assertion).include?(expected)
|
447
|
-
when Hash
|
448
|
-
assertion[:match].(email, parsed_emails(email), expected)
|
449
|
-
else
|
450
|
-
raise RuntimeError.new(
|
451
|
-
"Unsupported assertion mapping '#{assertion_match}' of type #{assertion_match.class.name}"
|
452
|
-
)
|
453
|
-
end
|
454
|
-
end
|
455
|
-
end
|
456
|
-
end
|