active_event_store 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8791927b35712c61094dcb6cc915e2b27912129e675c84230cd6f5271585902e
4
- data.tar.gz: fe58f1151444dc521caa4666adf06dd97d00bc37882843b1252d776683a98dcf
3
+ metadata.gz: e19560b568e8765b86d3925070dd1414b0de0545107e1dc1c7df4e3402148690
4
+ data.tar.gz: 87816ce897dc26614f8834993373bb271345c25934142f055547ad0f5023fe22
5
5
  SHA512:
6
- metadata.gz: 95f22443b58343d34b93b0400b53c63bf5190ff5f0c8e56c3c96c2e9ebf3d8137bee3136035f7f12d17f8c4488f8bdebef3a4e971eb67dfd17dbafdb48b857ba
7
- data.tar.gz: c947302a3db94490b5a781f3fb74bacc0b45d437240599766dd33138147b8807119604105b55034482861254463f4a49919dba2689d52eb16599459c2fe15b7a
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.0 (2021-01-14)
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:** Currently, we provide additional matchers only for RSpec. PRs with Minitest support are welcomed!
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveEventStore # :nodoc:
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.1"
5
5
  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.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-14 00:00:00.000000000 Z
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: