ruby_event_store-rspec 2.0.0 → 2.3.0

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: fd0942f48e1aea9cc5a5a67ffb5bbe5816c774f9d41db4bc0fda35e5061dd9d6
4
- data.tar.gz: 7376fe3939f833e8747fa3dd511de534a07616c45fd2f71704d05d325ae6d681
3
+ metadata.gz: 2aaf7916e409889b919cfb186ac2c7a0864658719a569138add1294941b66a6c
4
+ data.tar.gz: 1d03da7f66c3c055f07eaf066ebb1c780c39fc222e37fa92e31ff23918a13272
5
5
  SHA512:
6
- metadata.gz: cd0d6771ff5f9716171c2eb33209d5844bed93b24acef8704259d8784d97bbd756cb41ef7d20b3d265a35f90b3d5b9a514987017091f8aec51550958b0e1f5bf
7
- data.tar.gz: 2c1e227d7560312c0cc9f8934d1e501cbff3df854f109adf421c37909398931ccaefa1cc470e0aaae34578e141121160eb66853ac0dc902c11859704c8aeda5f
6
+ metadata.gz: 3b79ad1a3165e1355e3570a59242011570c39c47332a10947937d58b9f54881b65a9872b956736b5861840dbac5ff1ba21e3ea07e7c9238c62e2b1ac96504136
7
+ data.tar.gz: c4419133a70833970e69672a0b105c5076357753e073e88b6eadfe361a64516ddc8fda6a97d76bbc0020772a43e891391d0e873081a9d804273921ec052a6cfd
data/README.md CHANGED
@@ -1,206 +1,6 @@
1
- # RailsEventStore::RSpec
1
+ # RubyEventStore::RSpec
2
2
 
3
- ## Installation
3
+ RSpec matchers for RubyEventStore.
4
4
 
5
- Add this line to your application's Gemfile:
5
+ Find out more at [https://railseventstore.org](https://railseventstore.org/)
6
6
 
7
- ```ruby
8
- group :test do
9
- gem 'ruby_event_store-rspec'
10
- end
11
- ```
12
-
13
- ## Usage
14
-
15
- ### be_event
16
-
17
- The `be_event` matcher enables you to make expectations on a domain event. It exposes fluent interface.
18
-
19
- ```ruby
20
- OrderPlaced = Class.new(RubyEventStore::Event)
21
- domain_event = OrderPlaced.new(
22
- data: {
23
- order_id: 42,
24
- net_value: BigDecimal.new("1999.0")
25
- },
26
- metadata: {
27
- remote_ip: '1.2.3.4'
28
- }
29
- )
30
-
31
- expect(domain_event)
32
- .to(be_an_event(OrderPlaced)
33
- .with_data(order_id: 42, net_value: BigDecimal.new("1999.0"))
34
- .with_metadata(remote_ip: '1.2.3.4'))
35
- ```
36
-
37
- By default the behaviour of `with_data` and `with_metadata` is not strict, that is the expectation is met when all specified values for keys match. Additional data or metadata that is not specified to be expected does not change the outcome.
38
-
39
- ```ruby
40
- domain_event = OrderPlaced.new(
41
- data: {
42
- order_id: 42,
43
- net_value: BigDecimal.new("1999.0")
44
- }
45
- )
46
-
47
- # this would pass even though data contains also net_value
48
- expect(domain_event).to be_an_event(OrderPlaced).with_data(order_id: 42)
49
- ```
50
-
51
- This matcher is both [composable](http://rspec.info/blog/2014/01/new-in-rspec-3-composable-matchers/) and accepting [built-in matchers](https://relishapp.com/rspec/rspec-expectations/v/3-6/docs/built-in-matchers) as a part of an expectation.
52
-
53
- ```ruby
54
- expect(domain_event).to be_an_event(OrderPlaced).with_data(order_id: kind_of(Integer))
55
- expect([domain_event]).to include(an_event(OrderPlaced))
56
- ```
57
-
58
- If you depend on matching the exact data or metadata, there's a `strict` modifier.
59
-
60
- ```ruby
61
- domain_event = OrderPlaced.new(
62
- data: {
63
- order_id: 42,
64
- net_value: BigDecimal.new("1999.0")
65
- }
66
- )
67
-
68
- # this would fail as data contains unexpected net_value
69
- expect(domain_event).to be_an_event(OrderPlaced).with_data(order_id: 42).strict
70
- ```
71
-
72
- Mind that `strict` makes both `with_data` and `with_metadata` behave in a stricter way. If you need to mix both, i.e. strict data but non-strict metadata then consider composing matchers.
73
-
74
- ```ruby
75
- expect(domain_event)
76
- .to(be_event(OrderPlaced).with_data(order_id: 42, net_value: BigDecimal.new("1999.0")).strict
77
- .and(an_event(OrderPlaced).with_metadata(timestamp: kind_of(Time)))
78
- ```
79
-
80
- You may have noticed the same matcher being referenced as `be_event`, `be_an_event` and `an_event`. There's also just `event`. Use whichever reads better grammatically.
81
-
82
- ### have_published
83
-
84
- Use this matcher to target `event_store` and reading from streams specifically.
85
- In a simplest form it would read all streams backward up to a page limit (100 events) and check whether the expectation holds true. Its behaviour can be best compared to the `include` matcher — it is satisfied by at least one element present in the collection. You're encouraged to compose it with `be_event`.
86
-
87
- ```ruby
88
- event_store = RailsEventStore::Client.new
89
- event_store.publish(OrderPlaced.new(data: { order_id: 42 }))
90
-
91
- expect(event_store).to have_published(an_event(OrderPlaced))
92
- ```
93
-
94
- Expectation can be narrowed to the specific stream.
95
-
96
- ```ruby
97
- event_store = RailsEventStore::Client.new
98
- event_store.publish(OrderPlaced.new(data: { order_id: 42 }), stream_name: "Order$42")
99
-
100
- expect(event_store).to have_published(an_event(OrderPlaced)).in_stream("Order$42")
101
- ```
102
-
103
- It is sometimes important to ensure no additional events have been published. Luckliy there's a modifier to cover that usecase.
104
-
105
- ```ruby
106
- expect(event_store).not_to have_published(an_event(OrderPlaced)).once
107
- expect(event_store).to have_published(an_event(OrderPlaced)).exactly(2).times
108
- ```
109
-
110
- Finally you can make expectation on several events at once.
111
-
112
- ```ruby
113
- expect(event_store).to have_published(
114
- an_event(OrderPlaced),
115
- an_event(OrderExpired).with_data(expired_at: be_between(Date.yesterday, Date.tomorrow))
116
- )
117
- ```
118
-
119
- If there's a usecase not covered by examples above or you need a different set of events to make expectations on you can always resort to a more verbose approach and skip `have_published`.
120
-
121
- ```ruby
122
- expect(event_store.read_events_forward("OrderAuditLog$42", count: 2)).to eq([
123
- an_event(OrderPlaced),
124
- an_event(OrderExpired)
125
- ])
126
- ```
127
-
128
- ### have_applied
129
-
130
- This matcher is intended to be used on [aggregate root](https://github.com/RailsEventStore/rails_event_store/tree/master/aggregate_root#usage). Behaviour is almost identical to `have_published` counterpart, except the concept of stream. Expecations are made against internal unpublished events collection.
131
-
132
- ```ruby
133
- class Order
134
- include AggregateRoot
135
- HasBeenAlreadySubmitted = Class.new(StandardError)
136
- HasExpired = Class.new(StandardError)
137
-
138
- def initialize
139
- self.state = :new
140
- # any other code here
141
- end
142
-
143
- def submit
144
- raise HasBeenAlreadySubmitted if state == :submitted
145
- raise HasExpired if state == :expired
146
- apply OrderSubmitted.new(data: {delivery_date: Time.now + 24.hours})
147
- end
148
-
149
- def expire
150
- apply OrderExpired.new
151
- end
152
-
153
- private
154
- attr_accessor :state
155
-
156
- def apply_order_submitted(event)
157
- self.state = :submitted
158
- end
159
-
160
- def apply_order_expired(event)
161
- self.state = :expired
162
- end
163
- end
164
- ```
165
-
166
- ```ruby
167
- aggregate_root = Order.new
168
- aggregate_root.submit
169
-
170
- expect(aggregate_root).to have_applied(event(OrderSubmitted)).once
171
- ```
172
-
173
- ### have_subscribed_to_events
174
-
175
- Use this matcher to make sure that a handler has subscribed to events in target `event_store` or not.
176
-
177
- ```ruby
178
- event_store = RailsEventStore::Client.new
179
-
180
- # it will pass if Handler has subscribed to both events
181
- expect(Handler).to have_subscribed_to_events(FooEvent, BarEvent).in(event_store)
182
-
183
- # it will fail if Handler has subscribed to any event
184
- expect(Handler).not_to have_subscribed_to_events(FooEvent, BarEvent).in(event_store)
185
- ```
186
- ## Code status
187
-
188
- [![Build Status](https://travis-ci.org/RailsEventStore/rails_event_store-rspec.svg?branch=master)](https://travis-ci.org/RailsEventStore/rails_event_store-rspec)
189
- [![Gem Version](https://badge.fury.io/rb/rails_event_store-rspec.svg)](https://badge.fury.io/rb/rails_event_store-rspec)
190
-
191
- We're aiming for 100% mutation coverage in this project. This is why:
192
-
193
- * [Why I want to introduce mutation testing to the rails_event_store gem](https://blog.arkency.com/2015/04/why-i-want-to-introduce-mutation-testing-to-the-rails-event-store-gem/)
194
- * [Mutation testing and continuous integration](https://blog.arkency.com/2015/05/mutation-testing-and-continuous-integration/)
195
-
196
- Whenever you fix a bug or add a new feature, we require that the coverage doesn't go down.
197
-
198
- ## Development
199
-
200
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
201
-
202
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
203
-
204
- ## Contributing
205
-
206
- Bug reports and pull requests are welcome on GitHub at https://github.com/RailsEventStore/rails_event_store-rspec.
@@ -3,58 +3,51 @@
3
3
  module RubyEventStore
4
4
  module RSpec
5
5
  class Apply
6
+ def initialize(*expected, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
8
+ @failure_message_formatter = failure_message_formatter
9
+ @fetch_events = FetchUnpublishedEvents.new
10
+ end
11
+
6
12
  def in(aggregate)
7
- @aggregate = aggregate
13
+ fetch_events.in(aggregate)
8
14
  self
9
15
  end
10
16
 
11
17
  def strict
12
- @matcher = ::RSpec::Matchers::BuiltIn::Match.new(@expected)
18
+ expected.strict
13
19
  self
14
20
  end
15
21
 
16
- def matches?(event_proc)
17
- raise_aggregate_not_set unless @aggregate
18
- before = @aggregate.unpublished_events.to_a
19
- event_proc.call
20
- @applied_events = @aggregate.unpublished_events.to_a - before
21
- if match_events?
22
- @matcher.matches?(@applied_events)
23
- else
24
- !@applied_events.empty?
25
- end
22
+ def exactly(count)
23
+ expected.exactly(count)
24
+ self
26
25
  end
27
26
 
28
- def failure_message
29
- if match_events?
30
- <<-EOS
31
- expected block to have applied:
27
+ def times
28
+ self
29
+ end
30
+ alias :time :times
32
31
 
33
- #{@expected}
32
+ def once
33
+ expected.once
34
+ self
35
+ end
34
36
 
35
- but applied:
37
+ def matches?(event_proc)
38
+ raise_aggregate_not_set unless fetch_events.aggregate?
39
+ before = fetch_events.aggregate.unpublished_events.to_a
40
+ event_proc.call
41
+ @applied_events = fetch_events.aggregate.unpublished_events.to_a - before
42
+ MatchEvents.new.call(expected, applied_events)
43
+ end
36
44
 
37
- #{@applied_events}
38
- EOS
39
- else
40
- "expected block to have applied any events"
41
- end
45
+ def failure_message
46
+ failure_message_formatter.failure_message(expected, applied_events)
42
47
  end
43
48
 
44
49
  def failure_message_when_negated
45
- if match_events?
46
- <<-EOS
47
- expected block not to have applied:
48
-
49
- #{@expected}
50
-
51
- but applied:
52
-
53
- #{@applied_events}
54
- EOS
55
- else
56
- "expected block not to have applied any events"
57
- end
50
+ failure_message_formatter.failure_message_when_negated(expected, applied_events)
58
51
  end
59
52
 
60
53
  def description
@@ -67,18 +60,11 @@ EOS
67
60
 
68
61
  private
69
62
 
70
- def initialize(*expected)
71
- @expected = expected
72
- @matcher = ::RSpec::Matchers::BuiltIn::Include.new(*expected)
73
- end
74
-
75
- def match_events?
76
- !@expected.empty?
77
- end
78
-
79
63
  def raise_aggregate_not_set
80
- raise SyntaxError, "You have to set the aggregate instance with `in`, e.g. `expect { ... }.to apply(an_event(MyEvent)).in(aggregate)`"
64
+ raise "You have to set the aggregate instance with `in`, e.g. `expect { ... }.to apply(an_event(MyEvent)).in(aggregate)`"
81
65
  end
66
+
67
+ attr_reader :expected, :applied_events, :failure_message_formatter, :fetch_events
82
68
  end
83
69
  end
84
70
  end
@@ -90,7 +90,7 @@ module RubyEventStore
90
90
  end
91
91
 
92
92
  def to_s
93
- @expected && ["\n#{@label} diff:", @differ.diff_as_string(@actual.to_s, @expected.to_s)]
93
+ @expected && ["\n#{@label} diff:", @differ.diff(@actual.to_s + "\n", @expected.to_s)]
94
94
  end
95
95
  end
96
96
 
@@ -124,7 +124,7 @@ module RubyEventStore
124
124
 
125
125
  def matches?(actual)
126
126
  @actual = actual
127
- matches_kind && matches_data && matches_metadata
127
+ matches_kind?(actual) && matches_data?(actual) && matches_metadata?(actual)
128
128
  end
129
129
 
130
130
  def with_data(expected_data)
@@ -167,25 +167,27 @@ expected: not a kind of #{expected}
167
167
  " (#{expectation_list.join(" and ")})" if expectation_list.any?
168
168
  end
169
169
 
170
- private
170
+ attr_reader :expected, :expected_data, :expected_metadata
171
171
 
172
- def matches_kind
173
- KindMatcher.new(expected).matches?(actual)
172
+ def strict?
173
+ @strict
174
174
  end
175
175
 
176
- def matches_data
177
- DataMatcher.new(expected_data, strict: strict?).matches?(actual.data)
176
+ def matches_kind?(actual_event)
177
+ KindMatcher.new(expected).matches?(actual_event)
178
178
  end
179
179
 
180
- def matches_metadata
181
- DataMatcher.new(expected_metadata, strict: strict?).matches?(actual.metadata.to_h)
182
- end
180
+ private
183
181
 
184
- attr_reader :expected_metadata, :expected_data, :actual, :expected, :differ, :formatter
182
+ def matches_data?(actual_event)
183
+ DataMatcher.new(expected_data, strict: strict?).matches?(actual_event.data)
184
+ end
185
185
 
186
- def strict?
187
- @strict
186
+ def matches_metadata?(actual_event)
187
+ DataMatcher.new(expected_metadata, strict: strict?).matches?(actual_event.metadata.to_h)
188
188
  end
189
+
190
+ attr_reader :actual, :differ, :formatter
189
191
  end
190
192
  end
191
193
  end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module RSpec
5
+ class CrudeFailureMessageFormatter
6
+ class HavePublished
7
+ def initialize(differ)
8
+ @differ = differ
9
+ end
10
+
11
+ def failure_message(expected, events, stream_name)
12
+ stream_expectation = " in stream #{stream_name}" unless stream_name.nil?
13
+ "expected #{expected.events} to be published#{stream_expectation}, diff:" +
14
+ differ.diff(expected.events.to_s + "\n", events)
15
+ end
16
+
17
+ def failure_message_when_negated(expected, events, stream_name)
18
+ stream_expectation = " in stream #{stream_name}" unless stream_name.nil?
19
+ "expected #{expected.events} not to be published#{stream_expectation}, diff:" +
20
+ differ.diff(expected.events.to_s + "\n", events)
21
+ end
22
+
23
+ private
24
+ attr_reader :differ
25
+ end
26
+
27
+ class Publish
28
+ def failure_message(expected, events, stream)
29
+ if !expected.empty?
30
+ <<~EOS
31
+ expected block to have published:
32
+
33
+ #{expected.events}
34
+
35
+ #{"in stream #{stream} " if stream}but published:
36
+
37
+ #{events}
38
+ EOS
39
+ else
40
+ "expected block to have published any events"
41
+ end
42
+ end
43
+
44
+ def failure_message_when_negated(expected, events, stream)
45
+ if !expected.empty?
46
+ <<~EOS
47
+ expected block not to have published:
48
+
49
+ #{expected.events}
50
+
51
+ #{"in stream #{stream} " if stream}but published:
52
+
53
+ #{events}
54
+ EOS
55
+ else
56
+ "expected block not to have published any events"
57
+ end
58
+ end
59
+ end
60
+
61
+ class HaveApplied
62
+ def initialize(differ)
63
+ @differ = differ
64
+ end
65
+
66
+ def failure_message(expected, events)
67
+ "expected #{expected.events} to be applied, diff:" +
68
+ differ.diff(expected.events.to_s + "\n", events)
69
+ end
70
+
71
+ def failure_message_when_negated(expected, events)
72
+ "expected #{expected.events} not to be applied, diff:" +
73
+ differ.diff(expected.events.inspect + "\n", events)
74
+ end
75
+
76
+ attr_reader :differ
77
+ end
78
+
79
+ class Apply
80
+ def failure_message(expected, applied_events)
81
+ if !expected.empty?
82
+ <<~EOS
83
+ expected block to have applied:
84
+
85
+ #{expected.events}
86
+
87
+ but applied:
88
+
89
+ #{applied_events}
90
+ EOS
91
+ else
92
+ "expected block to have applied any events"
93
+ end
94
+ end
95
+
96
+ def failure_message_when_negated(expected, applied_events)
97
+ if !expected.empty?
98
+ <<~EOS
99
+ expected block not to have applied:
100
+
101
+ #{expected.events}
102
+
103
+ but applied:
104
+
105
+ #{applied_events}
106
+ EOS
107
+ else
108
+ "expected block not to have applied any events"
109
+ end
110
+ end
111
+ end
112
+
113
+ def have_published(differ)
114
+ HavePublished.new(differ)
115
+ end
116
+
117
+ def publish(_differ)
118
+ Publish.new
119
+ end
120
+
121
+ def have_applied(differ)
122
+ HaveApplied.new(differ)
123
+ end
124
+
125
+ def apply(_differ)
126
+ Apply.new
127
+ end
128
+ end
129
+ end
130
+ end