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