active_event_store 1.0.0 → 1.0.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/CHANGELOG.md +7 -1
- data/README.md +19 -3
- data/lib/active_event_store/test_helper/event_published_matcher.rb +150 -0
- data/lib/active_event_store/test_helper.rb +70 -0
- data/lib/active_event_store/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e19560b568e8765b86d3925070dd1414b0de0545107e1dc1c7df4e3402148690
|
4
|
+
data.tar.gz: 87816ce897dc26614f8834993373bb271345c25934142f055547ad0f5023fe22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: acf6017590c69d26066617c86ab63cd658c2307e8da89c53ec0afe18ebb4b384430bde9f632806f4e92a26293ed37cffa1c7abaa55ca2cd5f312d70d98ba873a
|
7
|
+
data.tar.gz: 181e900564fb3640a5484045ebbe6170b3871e05ea10a8e443bcadf7a530bf58bc227f4101d998a92ff96b82b569d1d24adcc1f97d3195c57fa2f0023c7db7db
|
data/CHANGELOG.md
CHANGED
@@ -2,9 +2,14 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
-
## 1.0.
|
5
|
+
## 1.0.1 (2021-09-16)
|
6
|
+
|
7
|
+
- Add minitest assertions: `assert_event_published`, `refute_event_published`, `assert_async_event_subscriber_enqueued` ([@chriscz][])
|
8
|
+
|
9
|
+
## 1.0.0 (2021-09-14)
|
6
10
|
|
7
11
|
- Ruby 2.6+, Rails 6+ and RailsEventStore 2.1+ is required.
|
12
|
+
|
8
13
|
## 0.2.1 (2020-09-30)
|
9
14
|
|
10
15
|
- Fix Active Support load hook name. ([@palkan][])
|
@@ -20,3 +25,4 @@ Now `ActiveSupport.on_load(:active_event_store) { ... }` works.
|
|
20
25
|
- Open source Active Event Store. ([@palkan][])
|
21
26
|
|
22
27
|
[@palkan]: https://github.com/palkan
|
28
|
+
[@chriscz]: https://github.com/chriscz
|
data/README.md
CHANGED
@@ -150,20 +150,32 @@ We suggest putting subscribers to the `app/subscribers` folder using the followi
|
|
150
150
|
|
151
151
|
You can test subscribers as normal Ruby objects.
|
152
152
|
|
153
|
-
**NOTE
|
153
|
+
**NOTE** To test using minitest include the `ActiveEventStore::TestHelpers` module in your tests.
|
154
154
|
|
155
155
|
To test that a given subscriber exists, you can use the `have_enqueued_async_subscriber_for` matcher:
|
156
156
|
|
157
157
|
```ruby
|
158
|
-
# for asynchronous subscriptions
|
158
|
+
# for asynchronous subscriptions (rspec)
|
159
159
|
it "is subscribed to some event" do
|
160
160
|
event = MyEvent.new(some: "data")
|
161
161
|
expect { ActiveEventStore.publish event }
|
162
162
|
.to have_enqueued_async_subscriber_for(MySubscriberService)
|
163
163
|
.with(event)
|
164
164
|
end
|
165
|
+
|
166
|
+
# for asynchronous subscriptions (minitest)
|
167
|
+
def test_is_subscribed_to_some_event
|
168
|
+
event = MyEvent.new(some: "data")
|
169
|
+
|
170
|
+
assert_async_event_subscriber_enqueued(MySubscriberService, event: event) do
|
171
|
+
ActiveEventStore.publish event
|
172
|
+
end
|
173
|
+
end
|
165
174
|
```
|
166
175
|
|
176
|
+
**NOTE** Async event subscribers are queued only after the current transaction has committed so when using `assert_enqued_async_subcriber` in rails
|
177
|
+
make sure to have `self.use_transactional_fixtures = false` at the top of your test class.
|
178
|
+
|
167
179
|
**NOTE:** You must have `rspec-rails` gem in your bundle to use `have_enqueued_async_subscriber_for` matcher.
|
168
180
|
|
169
181
|
For synchronous subscribers using `have_received` is enough:
|
@@ -183,10 +195,14 @@ end
|
|
183
195
|
To test event publishing, use `have_published_event` matcher:
|
184
196
|
|
185
197
|
```ruby
|
198
|
+
# rspec
|
186
199
|
expect { subject }.to have_published_event(ProfileCreated).with(user_id: user.id)
|
200
|
+
|
201
|
+
# minitest
|
202
|
+
assert_event_published(ProfileCreated, with: {user_id: user.id}) { subject }
|
187
203
|
```
|
188
204
|
|
189
|
-
**NOTE:** `have_published_event` only supports block expectations.
|
205
|
+
**NOTE:** `have_published_event` and `assert_event_published` only supports block expectations.
|
190
206
|
|
191
207
|
**NOTE 2** `with` modifier works like `have_attributes` matcher (not `contain_exactly`); you can only specify serializable attributes in `with` (i.e. sync attributes are not supported, 'cause they are not persistent).
|
192
208
|
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveEventStore
|
4
|
+
module TestHelper
|
5
|
+
class EventPublishedMatcher
|
6
|
+
attr_reader :attributes,
|
7
|
+
:matching_events
|
8
|
+
|
9
|
+
def initialize(expected_event_class, store: nil, with: nil, exactly: nil, at_least: nil, at_most: nil, refute: false)
|
10
|
+
@event_class = expected_event_class
|
11
|
+
@store = store || ActiveEventStore.event_store
|
12
|
+
@attributes = with
|
13
|
+
@refute = refute
|
14
|
+
|
15
|
+
count_expectations = {
|
16
|
+
exactly: exactly,
|
17
|
+
at_most: at_most,
|
18
|
+
at_least: at_least
|
19
|
+
}.reject { |_, v| v.nil? }
|
20
|
+
|
21
|
+
if count_expectations.length > 1
|
22
|
+
raise ArgumentError("Only one of :exactly, :at_least or :at_most can be specified")
|
23
|
+
elsif count_expectations.length == 0
|
24
|
+
@count_expectation_kind = :at_least
|
25
|
+
@expected_count = 1
|
26
|
+
else
|
27
|
+
@count_expectation_kind = count_expectations.keys.first
|
28
|
+
@expected_count = count_expectations.values.first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_published_events(&block)
|
33
|
+
original_count = @store.read.count
|
34
|
+
block.call
|
35
|
+
in_block_events(original_count, @store.read.count)
|
36
|
+
end
|
37
|
+
|
38
|
+
def matches?(block)
|
39
|
+
raise ArgumentError, "#{assertion_name} only support block assertions" if block.nil?
|
40
|
+
|
41
|
+
events = with_published_events do
|
42
|
+
block.call
|
43
|
+
end
|
44
|
+
|
45
|
+
@matching_events, @unmatching_events = partition_events(events)
|
46
|
+
|
47
|
+
mismatch_message = count_mismatch_message(@matching_events.size)
|
48
|
+
|
49
|
+
if mismatch_message
|
50
|
+
expectations = [
|
51
|
+
"Expected #{mismatch_message} #{@event_class.identifier}"
|
52
|
+
]
|
53
|
+
|
54
|
+
expectations << if refute?
|
55
|
+
report_events = @matching_events
|
56
|
+
"not to have been published"
|
57
|
+
else
|
58
|
+
report_events = @unmatching_events
|
59
|
+
"to have been published"
|
60
|
+
end
|
61
|
+
|
62
|
+
expectations << "with attributes #{attributes.inspect}" unless attributes.nil?
|
63
|
+
|
64
|
+
expectations << expectations.pop + ", but"
|
65
|
+
|
66
|
+
expectations << if report_events.any?
|
67
|
+
report_events.inject("published the following events instead:") do |msg, event|
|
68
|
+
msg + "\n #{event.inspect}"
|
69
|
+
end
|
70
|
+
else
|
71
|
+
"hasn't published anything"
|
72
|
+
end
|
73
|
+
|
74
|
+
return expectations.join(" ")
|
75
|
+
end
|
76
|
+
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def refute?
|
83
|
+
@refute
|
84
|
+
end
|
85
|
+
|
86
|
+
def assertion_name
|
87
|
+
if refute?
|
88
|
+
"refute_event_published"
|
89
|
+
else
|
90
|
+
"assert_event_published"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def negate_on_refute(cond)
|
95
|
+
if refute?
|
96
|
+
!cond
|
97
|
+
else
|
98
|
+
cond
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def in_block_events(before_block_count, after_block_count)
|
103
|
+
count_difference = after_block_count - before_block_count
|
104
|
+
if count_difference.positive?
|
105
|
+
@store.read.backward.limit(count_difference).to_a
|
106
|
+
else
|
107
|
+
[]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Partitions events into matching and unmatching
|
112
|
+
def partition_events(events)
|
113
|
+
events.partition { |e| self.class.event_matches?(@event_class, @attributes, e) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def count_mismatch_message(actual_count)
|
117
|
+
case @count_expectation_kind
|
118
|
+
when :exactly
|
119
|
+
if negate_on_refute(actual_count != @expected_count)
|
120
|
+
"exactly #{@expected_count}"
|
121
|
+
end
|
122
|
+
when :at_most
|
123
|
+
if negate_on_refute(actual_count > @expected_count)
|
124
|
+
"at most #{@expected_count}"
|
125
|
+
end
|
126
|
+
when :at_least
|
127
|
+
if negate_on_refute(actual_count < @expected_count)
|
128
|
+
"at least #{@expected_count}"
|
129
|
+
end
|
130
|
+
else
|
131
|
+
raise ArgumentError, "Unrecognized expectation kind: #{@count_expectation_kind}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class << self
|
136
|
+
def event_matches?(event_class, attributes, event)
|
137
|
+
event_type_matches?(event_class, event) && event_data_matches?(attributes, event)
|
138
|
+
end
|
139
|
+
|
140
|
+
def event_type_matches?(event_class, event)
|
141
|
+
event_class.identifier == event.event_type
|
142
|
+
end
|
143
|
+
|
144
|
+
def event_data_matches?(attributes, event)
|
145
|
+
(attributes.nil? || attributes.all? { |k, v| v == event.public_send(k) })
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_event_store/test_helper/event_published_matcher"
|
4
|
+
|
5
|
+
module ActiveEventStore
|
6
|
+
module TestHelper
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
include ActiveJob::TestHelper
|
11
|
+
end
|
12
|
+
|
13
|
+
# Asserts that the given event was published `exactly`, `at_least` or `at_most` number of times
|
14
|
+
# to a specific `store` `with` a particular hash of attributes.
|
15
|
+
def assert_event_published(expected_event, store: nil, with: nil, exactly: nil, at_least: nil, at_most: nil, &block)
|
16
|
+
matcher = EventPublishedMatcher.new(
|
17
|
+
expected_event,
|
18
|
+
store: store,
|
19
|
+
with: with,
|
20
|
+
exactly: exactly,
|
21
|
+
at_least: at_least,
|
22
|
+
at_most: at_most
|
23
|
+
)
|
24
|
+
|
25
|
+
if (msg = matcher.matches?(block))
|
26
|
+
fail(msg)
|
27
|
+
end
|
28
|
+
|
29
|
+
matcher.matching_events
|
30
|
+
end
|
31
|
+
|
32
|
+
# Asserts that the given event was *not* published `exactly`, `at_least` or `at_most` number of times
|
33
|
+
# to a specific `store` `with` a particular hash of attributes.
|
34
|
+
def refute_event_published(expected_event, store: nil, with: nil, exactly: nil, at_least: nil, at_most: nil, &block)
|
35
|
+
matcher = EventPublishedMatcher.new(
|
36
|
+
expected_event,
|
37
|
+
store: store,
|
38
|
+
with: with,
|
39
|
+
exactly: exactly,
|
40
|
+
at_least: at_least,
|
41
|
+
at_most: at_most,
|
42
|
+
refute: true
|
43
|
+
)
|
44
|
+
|
45
|
+
if (msg = matcher.matches?(block))
|
46
|
+
fail(msg)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def assert_async_event_subscriber_enqueued(subscriber_class, event: nil, queue: "events_subscribers", &block)
|
51
|
+
subscriber_job = ActiveEventStore::SubscriberJob.for(subscriber_class)
|
52
|
+
if subscriber_job.nil?
|
53
|
+
fail("No such async subscriber: #{subscriber_class.name}")
|
54
|
+
end
|
55
|
+
|
56
|
+
expected_event = event
|
57
|
+
event_matcher = ->(actual_event) { EventPublishedMatcher.event_matches?(expected_event, expected_event.data, actual_event) }
|
58
|
+
|
59
|
+
expected_args = if expected_event
|
60
|
+
event_matcher
|
61
|
+
end
|
62
|
+
|
63
|
+
assert_enqueued_with(job: subscriber_job, queue: queue, args: expected_args) do
|
64
|
+
ActiveRecord::Base.transaction do
|
65
|
+
block.call
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_event_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails_event_store
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.8'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.0'
|
83
97
|
description: Wrapper over Rails Event Store with conventions and transparent Rails
|
84
98
|
integration
|
85
99
|
email:
|
@@ -101,6 +115,8 @@ files:
|
|
101
115
|
- lib/active_event_store/rspec/have_enqueued_async_subscriber_for.rb
|
102
116
|
- lib/active_event_store/rspec/have_published_event.rb
|
103
117
|
- lib/active_event_store/subscriber_job.rb
|
118
|
+
- lib/active_event_store/test_helper.rb
|
119
|
+
- lib/active_event_store/test_helper/event_published_matcher.rb
|
104
120
|
- lib/active_event_store/version.rb
|
105
121
|
homepage: http://github.com/palkan/active_event_store
|
106
122
|
licenses:
|