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 +4 -4
- data/README.md +3 -203
- data/lib/ruby_event_store/rspec/apply.rb +32 -46
- data/lib/ruby_event_store/rspec/be_event.rb +15 -13
- data/lib/ruby_event_store/rspec/crude_failure_message_formatter.rb +130 -0
- data/lib/ruby_event_store/rspec/expected_collection.rb +43 -0
- data/lib/ruby_event_store/rspec/fetch_events.rb +33 -0
- data/lib/ruby_event_store/rspec/fetch_unpublished_events.rb +19 -0
- data/lib/ruby_event_store/rspec/have_applied.rb +15 -22
- data/lib/ruby_event_store/rspec/have_published.rb +28 -24
- data/lib/ruby_event_store/rspec/have_subscribed_to_events.rb +2 -2
- data/lib/ruby_event_store/rspec/match_events.rb +32 -0
- data/lib/ruby_event_store/rspec/matchers.rb +5 -5
- data/lib/ruby_event_store/rspec/publish.rb +47 -48
- data/lib/ruby_event_store/rspec/step_by_step_failure_message_formatter.rb +218 -0
- data/lib/ruby_event_store/rspec/version.rb +1 -1
- data/lib/ruby_event_store/rspec.rb +27 -9
- metadata +14 -15
- data/.mutant.yml +0 -1
- data/Gemfile +0 -10
- data/Gemfile.lock +0 -113
- data/Makefile +0 -21
- data/lib/rails_event_store/rspec.rb +0 -13
- data/rails_event_store-rspec.gemspec +0 -36
- data/ruby_event_store-rspec.gemspec +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2aaf7916e409889b919cfb186ac2c7a0864658719a569138add1294941b66a6c
|
4
|
+
data.tar.gz: 1d03da7f66c3c055f07eaf066ebb1c780c39fc222e37fa92e31ff23918a13272
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b79ad1a3165e1355e3570a59242011570c39c47332a10947937d58b9f54881b65a9872b956736b5861840dbac5ff1ba21e3ea07e7c9238c62e2b1ac96504136
|
7
|
+
data.tar.gz: c4419133a70833970e69672a0b105c5076357753e073e88b6eadfe361a64516ddc8fda6a97d76bbc0020772a43e891391d0e873081a9d804273921ec052a6cfd
|
data/README.md
CHANGED
@@ -1,206 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# RubyEventStore::RSpec
|
2
2
|
|
3
|
-
|
3
|
+
RSpec matchers for RubyEventStore.
|
4
4
|
|
5
|
-
|
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
|
-
|
13
|
+
fetch_events.in(aggregate)
|
8
14
|
self
|
9
15
|
end
|
10
16
|
|
11
17
|
def strict
|
12
|
-
|
18
|
+
expected.strict
|
13
19
|
self
|
14
20
|
end
|
15
21
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
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
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
def times
|
28
|
+
self
|
29
|
+
end
|
30
|
+
alias :time :times
|
32
31
|
|
33
|
-
|
32
|
+
def once
|
33
|
+
expected.once
|
34
|
+
self
|
35
|
+
end
|
34
36
|
|
35
|
-
|
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
|
-
|
38
|
-
|
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
|
-
|
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
|
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.
|
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
|
-
|
170
|
+
attr_reader :expected, :expected_data, :expected_metadata
|
171
171
|
|
172
|
-
def
|
173
|
-
|
172
|
+
def strict?
|
173
|
+
@strict
|
174
174
|
end
|
175
175
|
|
176
|
-
def
|
177
|
-
|
176
|
+
def matches_kind?(actual_event)
|
177
|
+
KindMatcher.new(expected).matches?(actual_event)
|
178
178
|
end
|
179
179
|
|
180
|
-
|
181
|
-
DataMatcher.new(expected_metadata, strict: strict?).matches?(actual.metadata.to_h)
|
182
|
-
end
|
180
|
+
private
|
183
181
|
|
184
|
-
|
182
|
+
def matches_data?(actual_event)
|
183
|
+
DataMatcher.new(expected_data, strict: strict?).matches?(actual_event.data)
|
184
|
+
end
|
185
185
|
|
186
|
-
def
|
187
|
-
|
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
|