delivery_matchers 0.1.0 → 0.1.1
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/.gitignore +11 -0
- data/.rspec +3 -0
- data/Appraisals +7 -0
- data/Gemfile +4 -0
- data/LICENSE.md +21 -0
- data/README.md +145 -0
- data/Rakefile +9 -0
- data/delivery_matchers.gemspec +25 -0
- data/gemfiles/actionmailer_4.gemfile +7 -0
- data/gemfiles/actionmailer_5.gemfile +7 -0
- data/lib/delivery_matchers.rb +11 -0
- data/lib/delivery_matchers/be_delivered.rb +158 -0
- data/lib/delivery_matchers/version.rb +3 -0
- metadata +32 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e78f4296323201bb6b45d5d38d1c6097e707d47
|
4
|
+
data.tar.gz: 4f3a1cb764fe4d9611ce5c6be03c5692d52e785a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 096a255d6cda92bda89e1ca0e271809aadf78b2482b66fbae7be2b4c3d9a54c81f5da66d7e93280235b5fb3f391868b6bbb86be1bd86929d3baf8f277f51475e
|
7
|
+
data.tar.gz: 4f4ab701739ba5d96659151263b5d3fb15bf0f651646810b1c444217da9fecf927087afbd08bdb7abd6a7917e350115bd95bfff6a4849133805d3c2ad143fdc6
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Appraisals
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 General Assembly
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# DeliveryMatchers
|
2
|
+
|
3
|
+
[](https://semaphoreci.com/generalassembly/delivery_matchers)
|
4
|
+
|
5
|
+
An RSpec custom matcher for ActionMailer's `deliver_later` method.
|
6
|
+
|
7
|
+
This matcher was extracted from a General Assembly product with a considerable amount of logic to determine when to send certain transactional emails to different subsets of students.
|
8
|
+
|
9
|
+
We wanted to ensure this code had bulletproof test coverage. Inspecting the in-memory ActiveJob queue let us test our delivery logic, but it also allowed a lot of low-level details to leak into our tests, making them harder to read.
|
10
|
+
|
11
|
+
We rolled this custom matcher to keep our test code beautiful. We hope you find it as useful as we did!
|
12
|
+
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'delivery_matchers'
|
20
|
+
```
|
21
|
+
|
22
|
+
and include the `DeliveryMatchers` module in your RSpec config:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
RSpec.configure do |config|
|
26
|
+
config.include DeliveryMatchers
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
### Basics
|
34
|
+
|
35
|
+
Let's assume you have a garden-variety ActionMailer class:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class UserMailer < ActionMailer::Base
|
39
|
+
def welcome(user)
|
40
|
+
@user = user
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Let's further assume you have a controller action that enqueues a welcome email for delivery with `deliver_later`, but only under certain circumstances:
|
46
|
+
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class UsersController < ApplicationController
|
50
|
+
def create
|
51
|
+
...
|
52
|
+
if user.emailable?
|
53
|
+
UserMailer(user).welcome.deliver_later
|
54
|
+
end
|
55
|
+
...
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
With this custom matcher, you can write tests to assert that the controller enqueues an email for delivery.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
expect( UserMailer.welcome(emailable_user) ).to be_delivered
|
64
|
+
```
|
65
|
+
|
66
|
+
You can also assert that an email is **not** delivered, in cases where that is the expected behavior.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
expect( UserMailer.welcome(non_emailable_user) ).not_to be_delivered
|
70
|
+
```
|
71
|
+
|
72
|
+
### Delivery options
|
73
|
+
|
74
|
+
#### wait_until
|
75
|
+
|
76
|
+
If you schedule an email for delivery on a future date with `wait_until`
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
UserMailer.welcome(user).deliver_later(wait_until: 1.day.from_now)
|
80
|
+
```
|
81
|
+
|
82
|
+
you can test it with
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
let(:email) { UserMailer.welcome(user) }
|
86
|
+
expect(email).to be_delivered 1.day.from_now
|
87
|
+
```
|
88
|
+
|
89
|
+
or with the more explicit (and sometimes more readable) form
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
expect(email).to be_delivered on: expected_date
|
93
|
+
```
|
94
|
+
|
95
|
+
#### wait
|
96
|
+
|
97
|
+
If you use `wait` to schedule an email for delivery after a certain interval
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
UserMailer.welcome(user).deliver_later(wait: 2.days)
|
101
|
+
```
|
102
|
+
|
103
|
+
you can test it like this
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
expect(email).to be_delivered in: 2.days
|
107
|
+
```
|
108
|
+
|
109
|
+
#### queue
|
110
|
+
|
111
|
+
You can also test that a delivery job was put into a particular queue
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
UserMailer.welcome(user).deliver_later(queue: "priority")
|
115
|
+
```
|
116
|
+
|
117
|
+
like this
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
expect(email).to be_delivered via_queue: "priority"
|
121
|
+
```
|
122
|
+
|
123
|
+
`via_queue` can also be combined with either the `on` or `in` options
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
expect(email).to be_delivered 3.days.from_now, via_queue: "priority"
|
127
|
+
|
128
|
+
expect(email).to be_delivered in: 2.days, via_queue: "priority"
|
129
|
+
```
|
130
|
+
|
131
|
+
### Alternative interface
|
132
|
+
|
133
|
+
`be_delivered` supports `in`, `on`, and `via_queue` as a convenience for readability. But if you prefer consistency with the code being tested, you can also use the same keys provided to `deliver_later`.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
expect(email).to be_delivered wait_until: expected_date
|
137
|
+
|
138
|
+
expect(email).to be_delivered wait: 2.days
|
139
|
+
|
140
|
+
expect(email).to be_delivered queue: "priority"
|
141
|
+
```
|
142
|
+
|
143
|
+
### A note on precision
|
144
|
+
|
145
|
+
This matcher performs all time comparisons with a precision of 1 second. If you see intermittent errors as a result, consider using [ActiveSupport's TimeHelpers](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html) or the [Timecop gem](https://github.com/travisjeffery/timecop) to freeze time in your tests.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'delivery_matchers/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "delivery_matchers"
|
8
|
+
spec.version = DeliveryMatchers::VERSION
|
9
|
+
spec.authors = ["General Assembly"]
|
10
|
+
spec.email = ["opensource@generalassemb.ly"]
|
11
|
+
|
12
|
+
spec.summary = "RSpec custom matchers for ActionMailer's deliver_later"
|
13
|
+
spec.homepage = "https://github.com/generalassembly/delivery_matchers"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
|
18
|
+
spec.add_dependency "rspec", ">= 3.0"
|
19
|
+
spec.add_dependency "actionmailer", ">= 4.2", "< 5.1"
|
20
|
+
spec.add_dependency "activejob", ">= 4.2", "< 5.1"
|
21
|
+
|
22
|
+
spec.add_development_dependency "appraisal"
|
23
|
+
spec.add_development_dependency "bundler"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module DeliveryMatchers
|
2
|
+
class BeDelivered
|
3
|
+
attr_reader :options, :email
|
4
|
+
|
5
|
+
def initialize(first={}, second={})
|
6
|
+
case first
|
7
|
+
when Time
|
8
|
+
# Allow user to specify the :on date as the first argument:
|
9
|
+
# be_delivered 1.day.from_now, via_queue: 'priority'
|
10
|
+
options = second
|
11
|
+
options[:on] = first
|
12
|
+
when Hash
|
13
|
+
options = first
|
14
|
+
end
|
15
|
+
|
16
|
+
# Rename the hash keys used by this matcher to match the keys used by
|
17
|
+
# ActionMailer::MessageDelivery#deliver_later
|
18
|
+
options[:wait] ||= options[:in]
|
19
|
+
options[:wait_until] ||= options[:on]
|
20
|
+
options[:queue] ||= options[:via_queue]
|
21
|
+
|
22
|
+
@options = options
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches?(email)
|
26
|
+
@email = email
|
27
|
+
enqueued_jobs.any? { |job| match_expected?(job) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def failure_message
|
31
|
+
enqueued = enqueued_jobs.map(&:inspect).join("\n ")
|
32
|
+
|
33
|
+
[
|
34
|
+
"expected to find this mail delivery job in queue:",
|
35
|
+
" #{expected_job}",
|
36
|
+
"instead found these jobs:",
|
37
|
+
" #{enqueued}"
|
38
|
+
].join("\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
def failure_message_when_negated
|
42
|
+
[
|
43
|
+
"expected NOT to find this mail delivery job in queue:",
|
44
|
+
" #{expected_job}"
|
45
|
+
].join("\n")
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def match_expected?(job)
|
51
|
+
args_match?(job) && options_match?(job)
|
52
|
+
end
|
53
|
+
|
54
|
+
def args_match?(job)
|
55
|
+
job[:args] == expected_args
|
56
|
+
end
|
57
|
+
|
58
|
+
def options_match?(job)
|
59
|
+
class_matches?(job) && queue_matches?(job) && time_matches?(job)
|
60
|
+
end
|
61
|
+
|
62
|
+
def class_matches?(job)
|
63
|
+
job[:job] == expected_class
|
64
|
+
end
|
65
|
+
|
66
|
+
def queue_matches?(job)
|
67
|
+
return true if options[:queue].nil?
|
68
|
+
job[:queue] == options[:queue]
|
69
|
+
end
|
70
|
+
|
71
|
+
def time_matches?(job)
|
72
|
+
return true unless options[:wait] || options[:wait_until]
|
73
|
+
|
74
|
+
if options[:wait]
|
75
|
+
expected = Time.current + options[:wait]
|
76
|
+
elsif options[:wait_until]
|
77
|
+
expected = options[:wait_until].to_time
|
78
|
+
end
|
79
|
+
|
80
|
+
# Difference between expected and actual must be less than 1 second
|
81
|
+
(job[:at].to_i - expected.to_i).abs <= 1
|
82
|
+
end
|
83
|
+
|
84
|
+
def expected_job
|
85
|
+
job = {
|
86
|
+
job: expected_class,
|
87
|
+
args: expected_args,
|
88
|
+
queue: expected_queue
|
89
|
+
}
|
90
|
+
|
91
|
+
if expected_delivery_time
|
92
|
+
job[:at] = expected_delivery_time
|
93
|
+
end
|
94
|
+
|
95
|
+
job
|
96
|
+
end
|
97
|
+
|
98
|
+
def expected_class
|
99
|
+
ActionMailer::DeliveryJob
|
100
|
+
end
|
101
|
+
|
102
|
+
def expected_queue
|
103
|
+
options[:queue] || 'mailers'
|
104
|
+
end
|
105
|
+
|
106
|
+
def expected_delivery_time
|
107
|
+
if options[:wait_until]
|
108
|
+
options[:wait_until].to_time.to_f
|
109
|
+
elsif options[:wait]
|
110
|
+
(Time.current + options[:wait]).to_f
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def expected_args
|
115
|
+
mailer = expected_mailer
|
116
|
+
method = expected_method
|
117
|
+
args = email.instance_variable_get('@args')
|
118
|
+
|
119
|
+
[mailer, method, 'deliver_now', *global_ids(args)]
|
120
|
+
end
|
121
|
+
|
122
|
+
def expected_mailer
|
123
|
+
if rails5?
|
124
|
+
email.instance_variable_get(:@mailer_class).to_s
|
125
|
+
else
|
126
|
+
email.instance_variable_get(:@mailer).to_s
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def expected_method
|
131
|
+
if rails5?
|
132
|
+
email.instance_variable_get(:@action).to_s
|
133
|
+
else
|
134
|
+
email.instance_variable_get(:@mail_method).to_s
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def global_ids(array)
|
139
|
+
Array(array).map { |obj| global_id(obj) }
|
140
|
+
end
|
141
|
+
|
142
|
+
def global_id(obj)
|
143
|
+
if obj.respond_to? :to_global_id
|
144
|
+
{ "_aj_globalid" => obj.to_global_id.to_s }
|
145
|
+
else
|
146
|
+
obj
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def enqueued_jobs
|
151
|
+
ActiveJob::Base.queue_adapter.enqueued_jobs
|
152
|
+
end
|
153
|
+
|
154
|
+
def rails5?
|
155
|
+
ActionMailer.version >= Gem::Version.new("5.0.0")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: delivery_matchers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- General Assembly
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -64,6 +64,20 @@ dependencies:
|
|
64
64
|
- - "<"
|
65
65
|
- !ruby/object:Gem::Version
|
66
66
|
version: '5.1'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: appraisal
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
67
81
|
- !ruby/object:Gem::Dependency
|
68
82
|
name: bundler
|
69
83
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,7 +112,20 @@ email:
|
|
98
112
|
executables: []
|
99
113
|
extensions: []
|
100
114
|
extra_rdoc_files: []
|
101
|
-
files:
|
115
|
+
files:
|
116
|
+
- ".gitignore"
|
117
|
+
- ".rspec"
|
118
|
+
- Appraisals
|
119
|
+
- Gemfile
|
120
|
+
- LICENSE.md
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- delivery_matchers.gemspec
|
124
|
+
- gemfiles/actionmailer_4.gemfile
|
125
|
+
- gemfiles/actionmailer_5.gemfile
|
126
|
+
- lib/delivery_matchers.rb
|
127
|
+
- lib/delivery_matchers/be_delivered.rb
|
128
|
+
- lib/delivery_matchers/version.rb
|
102
129
|
homepage: https://github.com/generalassembly/delivery_matchers
|
103
130
|
licenses: []
|
104
131
|
metadata: {}
|
@@ -118,8 +145,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
145
|
version: '0'
|
119
146
|
requirements: []
|
120
147
|
rubyforge_project:
|
121
|
-
rubygems_version: 2.
|
148
|
+
rubygems_version: 2.5.1
|
122
149
|
signing_key:
|
123
150
|
specification_version: 4
|
124
151
|
summary: RSpec custom matchers for ActionMailer's deliver_later
|
125
152
|
test_files: []
|
153
|
+
has_rdoc:
|