ruby_event_store-rspec 2.1.0 → 2.4.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: e1362df53ba19bf0a2194492d627ab17cd22ee658c9fefa47b5c61c0b8eec562
4
- data.tar.gz: c665c377cf751768cb1ec6abd8f486231ae382c6dafaef36e8cf0aaf01bc3800
3
+ metadata.gz: 8eb0afc118a3be2829fec2383858efaf7c8a1766a41db876e39969261b5dc017
4
+ data.tar.gz: a6c84fcff753ba1753eccda8029cff733885f4cd383bcd04a4976a591cf3c2d1
5
5
  SHA512:
6
- metadata.gz: 94e802c2bbed41e35aa8f8a2507cc127afe2e853e2266d60a56ea240f98c09f357bf7c664e7489de95a3f69d0b4892cd7f50a1c261fe2e7c995b531b45518fe4
7
- data.tar.gz: bf4cc3ec24389daf273187565731d7dc460e2d2ba9621605dafbbce46b19df9324f27cc98deb8c749955e15b10a3c6dd8167f36ae07fbea0592a2c2b8e884261
6
+ metadata.gz: 6f7db18e2cef98b5af008f636bc04fbdbb89763eb5eec488fa9fdc439d95793329c34e8ce17d2b1f0bd5423a767772e2c1dfae34867e3ff8b87c5c7d9de1a44f
7
+ data.tar.gz: 28be5f30b48d48b8d643f75afde951a706a96fbac8b3d3472f8e890e42c2e2ba2b02e78f6fd5836982021d4b601f02eb98da09a09d13497830f898a0d6d35e07
data/README.md CHANGED
@@ -3,4 +3,3 @@
3
3
  RSpec matchers for RubyEventStore.
4
4
 
5
5
  Find out more at [https://railseventstore.org](https://railseventstore.org/)
6
-
@@ -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
@@ -29,9 +29,9 @@ module RubyEventStore
29
29
  class FailureMessage
30
30
  class ExpectedLine
31
31
  def initialize(expected_klass, expected_metadata, expected_data)
32
- @expected_klass = expected_klass
32
+ @expected_klass = expected_klass
33
33
  @expected_metadata = expected_metadata
34
- @expected_data = expected_data
34
+ @expected_data = expected_data
35
35
  end
36
36
 
37
37
  def to_s
@@ -55,11 +55,11 @@ module RubyEventStore
55
55
 
56
56
  class ActualLine
57
57
  def initialize(actual_klass, actual_metadata, actual_data, expected_metadata, expected_data)
58
- @actual_klass = actual_klass
59
- @actual_metadata = actual_metadata
60
- @actual_data = actual_data
58
+ @actual_klass = actual_klass
59
+ @actual_metadata = actual_metadata
60
+ @actual_data = actual_data
61
61
  @expected_metadata = expected_metadata
62
- @expected_data = expected_data
62
+ @expected_data = expected_data
63
63
  end
64
64
 
65
65
  def to_s
@@ -83,25 +83,33 @@ module RubyEventStore
83
83
 
84
84
  class Diff
85
85
  def initialize(actual, expected, label, differ:)
86
- @actual = actual
86
+ @actual = actual
87
87
  @expected = expected
88
- @label = label
89
- @differ = differ
88
+ @label = label
89
+ @differ = differ
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
 
97
- def initialize(expected_klass, actual_klass, expected_data, actual_data, expected_metadata, actual_metadata, differ:)
98
- @expected_klass = expected_klass
99
- @actual_klass = actual_klass
100
- @expected_data = expected_data
101
- @actual_data = actual_data
97
+ def initialize(
98
+ expected_klass,
99
+ actual_klass,
100
+ expected_data,
101
+ actual_data,
102
+ expected_metadata,
103
+ actual_metadata,
104
+ differ:
105
+ )
106
+ @expected_klass = expected_klass
107
+ @actual_klass = actual_klass
108
+ @expected_data = expected_data
109
+ @actual_data = actual_data
102
110
  @expected_metadata = expected_metadata
103
- @actual_metadata = actual_metadata
104
- @differ = differ
111
+ @actual_metadata = actual_metadata
112
+ @differ = differ
105
113
  end
106
114
 
107
115
  def to_s
@@ -117,14 +125,14 @@ module RubyEventStore
117
125
  include ::RSpec::Matchers::Composable
118
126
 
119
127
  def initialize(expected, differ:, formatter:)
120
- @expected = expected
121
- @differ = differ
128
+ @expected = expected
129
+ @differ = differ
122
130
  @formatter = formatter
123
131
  end
124
132
 
125
133
  def matches?(actual)
126
134
  @actual = actual
127
- matches_kind && matches_data && matches_metadata
135
+ matches_kind?(actual) && matches_data?(actual) && matches_metadata?(actual)
128
136
  end
129
137
 
130
138
  def with_data(expected_data)
@@ -138,16 +146,24 @@ module RubyEventStore
138
146
  end
139
147
 
140
148
  def failure_message
141
- actual_data = actual.data if actual.respond_to?(:data)
149
+ actual_data = actual.data if actual.respond_to?(:data)
142
150
  actual_metadata = actual.metadata if actual.respond_to?(:metadata)
143
- FailureMessage.new(expected, actual.class, expected_data, actual_data, expected_metadata, actual_metadata, differ: differ).to_s
151
+ FailureMessage.new(
152
+ expected,
153
+ actual.class,
154
+ expected_data,
155
+ actual_data,
156
+ expected_metadata,
157
+ actual_metadata,
158
+ differ: differ
159
+ ).to_s
144
160
  end
145
161
 
146
162
  def failure_message_when_negated
147
- %Q{
163
+ "
148
164
  expected: not a kind of #{expected}
149
165
  got: #{actual.class}
150
- }
166
+ "
151
167
  end
152
168
 
153
169
  def strict
@@ -167,26 +183,27 @@ expected: not a kind of #{expected}
167
183
  " (#{expectation_list.join(" and ")})" if expectation_list.any?
168
184
  end
169
185
 
170
- private
186
+ attr_reader :expected, :expected_data, :expected_metadata
171
187
 
172
- def matches_kind
173
- KindMatcher.new(expected).matches?(actual)
188
+ def strict?
189
+ @strict
174
190
  end
175
191
 
176
- def matches_data
177
- DataMatcher.new(expected_data, strict: strict?).matches?(actual.data)
192
+ def matches_kind?(actual_event)
193
+ KindMatcher.new(expected).matches?(actual_event)
178
194
  end
179
195
 
180
- def matches_metadata
181
- DataMatcher.new(expected_metadata, strict: strict?).matches?(actual.metadata.to_h)
182
- end
196
+ private
183
197
 
184
- attr_reader :expected_metadata, :expected_data, :actual, :expected, :differ, :formatter
198
+ def matches_data?(actual_event)
199
+ DataMatcher.new(expected_data, strict: strict?).matches?(actual_event.data)
200
+ end
185
201
 
186
- def strict?
187
- @strict
202
+ def matches_metadata?(actual_event)
203
+ DataMatcher.new(expected_metadata, strict: strict?).matches?(actual_event.metadata.to_h)
188
204
  end
205
+
206
+ attr_reader :actual, :differ, :formatter
189
207
  end
190
208
  end
191
209
  end
192
-
@@ -0,0 +1,129 @@
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
+
25
+ attr_reader :differ
26
+ end
27
+
28
+ class Publish
29
+ def failure_message(expected, events, stream)
30
+ if !expected.empty?
31
+ <<~EOS
32
+ expected block to have published:
33
+
34
+ #{expected.events}
35
+
36
+ #{"in stream #{stream} " if stream}but published:
37
+
38
+ #{events}
39
+ EOS
40
+ else
41
+ "expected block to have published any events"
42
+ end
43
+ end
44
+
45
+ def failure_message_when_negated(expected, events, stream)
46
+ if !expected.empty?
47
+ <<~EOS
48
+ expected block not to have published:
49
+
50
+ #{expected.events}
51
+
52
+ #{"in stream #{stream} " if stream}but published:
53
+
54
+ #{events}
55
+ EOS
56
+ else
57
+ "expected block not to have published any events"
58
+ end
59
+ end
60
+ end
61
+
62
+ class HaveApplied
63
+ def initialize(differ)
64
+ @differ = differ
65
+ end
66
+
67
+ def failure_message(expected, events)
68
+ "expected #{expected.events} to be applied, diff:" + 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:" + differ.diff(expected.events.inspect + "\n", events)
73
+ end
74
+
75
+ attr_reader :differ
76
+ end
77
+
78
+ class Apply
79
+ def failure_message(expected, applied_events)
80
+ if !expected.empty?
81
+ <<~EOS
82
+ expected block to have applied:
83
+
84
+ #{expected.events}
85
+
86
+ but applied:
87
+
88
+ #{applied_events}
89
+ EOS
90
+ else
91
+ "expected block to have applied any events"
92
+ end
93
+ end
94
+
95
+ def failure_message_when_negated(expected, applied_events)
96
+ if !expected.empty?
97
+ <<~EOS
98
+ expected block not to have applied:
99
+
100
+ #{expected.events}
101
+
102
+ but applied:
103
+
104
+ #{applied_events}
105
+ EOS
106
+ else
107
+ "expected block not to have applied any events"
108
+ end
109
+ end
110
+ end
111
+
112
+ def have_published(differ)
113
+ HavePublished.new(differ)
114
+ end
115
+
116
+ def publish(_differ)
117
+ Publish.new
118
+ end
119
+
120
+ def have_applied(differ)
121
+ HaveApplied.new(differ)
122
+ end
123
+
124
+ def apply(_differ)
125
+ Apply.new
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,43 @@
1
+ module RubyEventStore
2
+ module RSpec
3
+ class ExpectedCollection
4
+ def initialize(events)
5
+ @events = events
6
+ @strict = false
7
+ end
8
+
9
+ def exactly(count)
10
+ raise NotSupported if !events.size.equal?(1)
11
+ raise NotSupported if count < 1
12
+ @count = count
13
+ end
14
+
15
+ def empty?
16
+ events.empty?
17
+ end
18
+
19
+ def once
20
+ exactly(1)
21
+ end
22
+
23
+ def specified_count?
24
+ !count.nil?
25
+ end
26
+
27
+ def strict
28
+ @strict = true
29
+ end
30
+
31
+ def strict?
32
+ @strict
33
+ end
34
+
35
+ def event
36
+ raise NotSupported if !events.size.equal?(1)
37
+ events.first
38
+ end
39
+
40
+ attr_reader :events, :count
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ module RubyEventStore
2
+ module RSpec
3
+ class FetchEvents
4
+ MissingEventStore = Class.new(StandardError)
5
+
6
+ def from(event_id)
7
+ @start = event_id
8
+ end
9
+
10
+ def stream(stream_name)
11
+ @stream_name = stream_name
12
+ end
13
+
14
+ def in(event_store)
15
+ @event_store = event_store
16
+ end
17
+
18
+ def from_last
19
+ @start = call.last&.event_id
20
+ end
21
+
22
+ def call
23
+ raise MissingEventStore if event_store.nil?
24
+ events = event_store.read
25
+ events = events.stream(stream_name) if stream_name
26
+ events = events.from(start) if start
27
+ events
28
+ end
29
+
30
+ attr_reader :start, :stream_name, :event_store
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,19 @@
1
+ module RubyEventStore
2
+ module RSpec
3
+ class FetchUnpublishedEvents
4
+ def in(aggregate)
5
+ @aggregate = aggregate
6
+ end
7
+
8
+ def call
9
+ aggregate.unpublished_events.to_a
10
+ end
11
+
12
+ def aggregate?
13
+ !aggregate.nil?
14
+ end
15
+
16
+ attr_reader :aggregate
17
+ end
18
+ end
19
+ end
@@ -3,61 +3,54 @@
3
3
  module RubyEventStore
4
4
  module RSpec
5
5
  class HaveApplied
6
- def initialize(mandatory_expected, *optional_expected, differ:, phraser:)
7
- @expected = [mandatory_expected, *optional_expected]
8
- @matcher = ::RSpec::Matchers::BuiltIn::Include.new(*expected)
9
- @differ = differ
10
- @phraser = phraser
6
+ def initialize(*expected, phraser:, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
8
+ @failure_message_formatter = failure_message_formatter
9
+ @phraser = phraser
10
+ @fetch_events = FetchUnpublishedEvents.new
11
11
  end
12
12
 
13
13
  def matches?(aggregate_root)
14
- @events = aggregate_root.unpublished_events.to_a
15
- matcher.matches?(events) && matches_count?
14
+ fetch_events.in(aggregate_root)
15
+ @events = fetch_events.call
16
+ MatchEvents.new.call(expected, events)
16
17
  end
17
18
 
18
19
  def exactly(count)
19
- @count = count
20
+ expected.exactly(count)
20
21
  self
21
22
  end
22
23
 
23
24
  def times
24
25
  self
25
26
  end
26
- alias :time :times
27
+ alias time times
27
28
 
28
29
  def once
29
- exactly(1)
30
+ expected.once
31
+ self
30
32
  end
31
33
 
32
34
  def strict
33
- @matcher = ::RSpec::Matchers::BuiltIn::Match.new(expected)
35
+ expected.strict
34
36
  self
35
37
  end
36
38
 
37
39
  def failure_message
38
- "expected #{expected} to be applied, diff:" +
39
- differ.diff_as_string(expected.to_s, events.to_s)
40
+ failure_message_formatter.failure_message(expected, events)
40
41
  end
41
42
 
42
43
  def failure_message_when_negated
43
- "expected #{expected} not to be applied, diff:" +
44
- differ.diff_as_string(expected.inspect, events.inspect)
44
+ failure_message_formatter.failure_message_when_negated(expected, events)
45
45
  end
46
46
 
47
47
  def description
48
- "have applied events that have to (#{phraser.(expected)})"
48
+ "have applied events that have to (#{phraser.(expected.events)})"
49
49
  end
50
50
 
51
51
  private
52
52
 
53
- def matches_count?
54
- return true unless count
55
- raise NotSupported if expected.size > 1
56
- events.select { |e| expected.first === e }.size.equal?(count)
57
- end
58
-
59
- attr_reader :differ, :phraser, :expected, :events, :count, :matcher
53
+ attr_reader :phraser, :expected, :events, :failure_message_formatter, :fetch_events
60
54
  end
61
55
  end
62
56
  end
63
-
@@ -3,73 +3,77 @@
3
3
  module RubyEventStore
4
4
  module RSpec
5
5
  class HavePublished
6
- def initialize(mandatory_expected, *optional_expected, differ:, phraser:)
7
- @expected = [mandatory_expected, *optional_expected]
8
- @matcher = ::RSpec::Matchers::BuiltIn::Include.new(*expected)
9
- @differ = differ
10
- @phraser = phraser
6
+ def initialize(*expected, phraser:, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
8
+ @phraser = phraser
9
+ @failure_message_formatter = failure_message_formatter
10
+ @fetch_events = FetchEvents.new
11
11
  end
12
12
 
13
13
  def matches?(event_store)
14
- @events = event_store.read
15
- @events = events.stream(stream_name) if stream_name
16
- @events = events.from(start) if start
17
- @events = events.each
18
- @matcher.matches?(events) && matches_count?
14
+ stream_names.all? do |stream_name|
15
+ fetch_events.stream(stream_name)
16
+ fetch_events.in(event_store)
17
+ @published_events = fetch_events.call.to_a
18
+ @failed_on_stream = stream_name
19
+ MatchEvents.new.call(expected, published_events)
20
+ end
19
21
  end
20
22
 
21
23
  def exactly(count)
22
- @count = count
24
+ expected.exactly(count)
23
25
  self
24
26
  end
25
27
 
26
28
  def in_stream(stream_name)
27
- @stream_name = stream_name
29
+ @stream_names = [stream_name]
30
+ self
31
+ end
32
+
33
+ def in_streams(stream_names)
34
+ @stream_names = Array(stream_names)
28
35
  self
29
36
  end
30
37
 
31
38
  def times
32
39
  self
33
40
  end
34
- alias :time :times
41
+ alias time times
35
42
 
36
43
  def from(event_id)
37
- @start = event_id
44
+ fetch_events.from(event_id)
38
45
  self
39
46
  end
40
47
 
41
48
  def once
42
- exactly(1)
49
+ expected.once
50
+ self
43
51
  end
44
52
 
45
53
  def failure_message
46
- "expected #{expected} to be published, diff:" +
47
- differ.diff_as_string(expected.to_s, events.to_a.to_s)
54
+ failure_message_formatter.failure_message(expected, published_events, failed_on_stream)
48
55
  end
49
56
 
50
57
  def failure_message_when_negated
51
- "expected #{expected} not to be published, diff:" +
52
- differ.diff_as_string(expected.to_s, events.to_a.to_s)
58
+ failure_message_formatter.failure_message_when_negated(expected, published_events, failed_on_stream)
53
59
  end
54
60
 
55
61
  def description
56
- "have published events that have to (#{phraser.(expected)})"
62
+ "have published events that have to (#{phraser.(expected.events)})"
57
63
  end
58
64
 
59
65
  def strict
60
- @matcher = ::RSpec::Matchers::BuiltIn::Match.new(expected)
66
+ expected.strict
61
67
  self
62
68
  end
63
69
 
64
70
  private
65
71
 
66
- def matches_count?
67
- return true unless count
68
- raise NotSupported if expected.size > 1
69
- events.select { |e| expected.first === e }.size.equal?(count)
72
+ def stream_names
73
+ @stream_names || [nil]
70
74
  end
71
75
 
72
- attr_reader :differ, :phraser, :stream_name, :expected, :count, :events, :start
76
+ attr_reader :phraser, :expected, :published_events, :failed_on_stream, :failure_message_formatter, :fetch_events
73
77
  end
74
78
  end
75
79
  end
@@ -12,9 +12,7 @@ module RubyEventStore
12
12
 
13
13
  def matches?(handler)
14
14
  @handler = handler
15
- @subscribed_to = expected.select do |event|
16
- event_store.subscribers_for(event).include?(handler)
17
- end
15
+ @subscribed_to = expected.select { |event| event_store.subscribers_for(event).include?(handler) }
18
16
 
19
17
  matcher.matches?(subscribed_to)
20
18
  end
@@ -25,13 +23,11 @@ module RubyEventStore
25
23
  end
26
24
 
27
25
  def failure_message
28
- "expected #{handler} to be subscribed to events, diff:" +
29
- differ.diff_as_string(expected.to_s, subscribed_to.to_s)
26
+ "expected #{handler} to be subscribed to events, diff:" + differ.diff(expected.to_s + "\n", subscribed_to)
30
27
  end
31
28
 
32
29
  def failure_message_when_negated
33
- "expected #{handler} not to be subscribed to events, diff:" +
34
- differ.diff_as_string(expected.to_s, subscribed_to.to_s)
30
+ "expected #{handler} not to be subscribed to events, diff:" + differ.diff(expected.to_s + "\n", subscribed_to)
35
31
  end
36
32
 
37
33
  def description
@@ -40,9 +36,7 @@ module RubyEventStore
40
36
 
41
37
  private
42
38
 
43
- attr_reader :expected, :handler, :subscribed_to,
44
- :differ, :phraser, :matcher, :event_store
39
+ attr_reader :expected, :handler, :subscribed_to, :differ, :phraser, :matcher, :event_store
45
40
  end
46
41
  end
47
42
  end
48
-
@@ -0,0 +1,32 @@
1
+ module RubyEventStore
2
+ module RSpec
3
+ class MatchEvents
4
+ def call(expected, events)
5
+ if match_events?(expected)
6
+ matcher(expected).matches?(events) && matches_count?(expected, events)
7
+ else
8
+ !events.empty?
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def matches_count?(expected, events)
15
+ return true unless expected.specified_count?
16
+ events.select { |e| expected.event === e }.size.equal?(expected.count)
17
+ end
18
+
19
+ def matcher(expected)
20
+ if expected.strict?
21
+ ::RSpec::Matchers::BuiltIn::Match.new(expected.events)
22
+ else
23
+ ::RSpec::Matchers::BuiltIn::Include.new(*expected.events)
24
+ end
25
+ end
26
+
27
+ def match_events?(expected)
28
+ !expected.empty?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -8,11 +8,7 @@ module RubyEventStore
8
8
  def call(object)
9
9
  items = Array(object).compact.map { |o| format(o) }
10
10
  return "" if items.empty?
11
- if items.one?
12
- items.join
13
- else
14
- "#{items[all_but_last].join(", ")} and #{items.fetch(-1)}"
15
- end
11
+ items.one? ? items.join : "#{items[all_but_last].join(", ")} and #{items.fetch(-1)}"
16
12
  end
17
13
 
18
14
  private
@@ -22,11 +18,7 @@ module RubyEventStore
22
18
  end
23
19
 
24
20
  def format(object)
25
- if object.respond_to?(:description)
26
- ::RSpec::Support::ObjectFormatter.format(object)
27
- else
28
- "be a #{object}"
29
- end
21
+ object.respond_to?(:description) ? ::RSpec::Support::ObjectFormatter.format(object) : "be a #{object}"
30
22
  end
31
23
  end
32
24
  end
@@ -34,16 +26,24 @@ module RubyEventStore
34
26
  def be_an_event(expected)
35
27
  BeEvent.new(expected, differ: differ, formatter: formatter)
36
28
  end
37
- alias :be_event :be_an_event
38
- alias :an_event :be_an_event
39
- alias :event :be_an_event
29
+ alias be_event be_an_event
30
+ alias an_event be_an_event
31
+ alias event be_an_event
40
32
 
41
33
  def have_published(*expected)
42
- HavePublished.new(*expected, differ: differ, phraser: phraser)
34
+ HavePublished.new(
35
+ *expected,
36
+ phraser: phraser,
37
+ failure_message_formatter: RSpec.default_formatter.have_published(differ)
38
+ )
43
39
  end
44
40
 
45
41
  def have_applied(*expected)
46
- HaveApplied.new(*expected, differ: differ, phraser: phraser)
42
+ HaveApplied.new(
43
+ *expected,
44
+ phraser: phraser,
45
+ failure_message_formatter: RSpec.default_formatter.have_applied(differ)
46
+ )
47
47
  end
48
48
 
49
49
  def have_subscribed_to_events(*expected)
@@ -51,11 +51,11 @@ module RubyEventStore
51
51
  end
52
52
 
53
53
  def publish(*expected)
54
- Publish.new(*expected)
54
+ Publish.new(*expected, failure_message_formatter: RSpec.default_formatter.publish(differ))
55
55
  end
56
56
 
57
57
  def apply(*expected)
58
- Apply.new(*expected)
58
+ Apply.new(*expected, failure_message_formatter: RSpec.default_formatter.apply(differ))
59
59
  end
60
60
 
61
61
  private
@@ -65,7 +65,7 @@ module RubyEventStore
65
65
  end
66
66
 
67
67
  def differ
68
- ::RSpec::Support::Differ.new(color: ::RSpec::Matchers.configuration.color?)
68
+ ::RSpec::Expectations.differ
69
69
  end
70
70
 
71
71
  def phraser
@@ -3,61 +3,66 @@
3
3
  module RubyEventStore
4
4
  module RSpec
5
5
  class Publish
6
+ def initialize(*expected, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
8
+ @failure_message_formatter = failure_message_formatter
9
+ @fetch_events = FetchEvents.new
10
+ end
11
+
6
12
  def in(event_store)
7
- @event_store = event_store
13
+ fetch_events.in(event_store)
8
14
  self
9
15
  end
10
16
 
11
- def in_stream(stream)
12
- @stream = stream
17
+ def in_stream(stream_name)
18
+ @stream_names = [stream_name]
13
19
  self
14
20
  end
15
21
 
16
- def matches?(event_proc)
17
- raise_event_store_not_set unless @event_store
18
- spec = @event_store.read
19
- spec = spec.stream(@stream) if @stream
20
- last_event_before_block = spec.last
21
- event_proc.call
22
- spec = spec.from(last_event_before_block.event_id) if last_event_before_block
23
- @published_events = spec.to_a
24
- if match_events?
25
- ::RSpec::Matchers::BuiltIn::Include.new(*@expected).matches?(@published_events)
26
- else
27
- !@published_events.empty?
28
- end
22
+ def in_streams(stream_names)
23
+ @stream_names = Array(stream_names)
24
+ self
29
25
  end
30
26
 
31
- def failure_message
32
- if match_events?
33
- <<-EOS
34
- expected block to have published:
35
-
36
- #{@expected}
27
+ def exactly(count)
28
+ expected.exactly(count)
29
+ self
30
+ end
37
31
 
38
- #{"in stream #{@stream} " if @stream}but published:
32
+ def once
33
+ expected.once
34
+ self
35
+ end
39
36
 
40
- #{@published_events}
41
- EOS
42
- else
43
- "expected block to have published any events"
44
- end
37
+ def times
38
+ self
45
39
  end
40
+ alias time times
46
41
 
47
- def failure_message_when_negated
48
- if match_events?
49
- <<-EOS
50
- expected block not to have published:
42
+ def strict
43
+ expected.strict
44
+ self
45
+ end
51
46
 
52
- #{@expected}
47
+ def matches?(event_proc)
48
+ fetch_events.from_last
49
+ event_proc.call
50
+ stream_names.all? do |stream_name|
51
+ fetch_events.stream(stream_name)
52
+ @published_events = fetch_events.call.to_a
53
+ @failed_on_stream = stream_name
54
+ MatchEvents.new.call(expected, published_events)
55
+ end
56
+ rescue FetchEvents::MissingEventStore
57
+ raise "You have to set the event store instance with `in`, e.g. `expect { ... }.to publish(an_event(MyEvent)).in(event_store)`"
58
+ end
53
59
 
54
- #{"in stream #{@stream} " if @stream}but published:
60
+ def failure_message
61
+ failure_message_formatter.failure_message(expected, published_events, failed_on_stream)
62
+ end
55
63
 
56
- #{@published_events}
57
- EOS
58
- else
59
- "expected block not to have published any events"
60
- end
64
+ def failure_message_when_negated
65
+ failure_message_formatter.failure_message_when_negated(expected, published_events, fetch_events.stream_name)
61
66
  end
62
67
 
63
68
  def description
@@ -70,17 +75,11 @@ EOS
70
75
 
71
76
  private
72
77
 
73
- def initialize(*expected)
74
- @expected = expected
78
+ def stream_names
79
+ @stream_names || [nil]
75
80
  end
76
81
 
77
- def match_events?
78
- !@expected.empty?
79
- end
80
-
81
- def raise_event_store_not_set
82
- raise SyntaxError, "You have to set the event store instance with `in`, e.g. `expect { ... }.to publish(an_event(MyEvent)).in(event_store)`"
83
- end
82
+ attr_reader :fetch_events, :expected, :failure_message_formatter, :published_events, :failed_on_stream
84
83
  end
85
84
  end
86
85
  end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module RSpec
5
+ class StepByStepFailureMessageFormatter
6
+ class Lingo
7
+ def initialize(published)
8
+ @published = published
9
+ end
10
+ attr_reader :published
11
+ end
12
+
13
+ class HavePublished
14
+ def initialize(differ, lingo)
15
+ @differ = differ
16
+ @lingo = lingo
17
+ end
18
+
19
+ def failure_message(expected, events, stream_name = nil)
20
+ return failure_message_strict(expected, events) if expected.strict?
21
+ return failure_message_no_events if expected.empty?
22
+ expected.events.each do |expected_event|
23
+ correct_event_count = 0
24
+ events_with_correct_type = []
25
+ events.each do |actual_event|
26
+ if expected_event.matches?(actual_event)
27
+ correct_event_count += 1
28
+ elsif expected_event.matches_kind?(actual_event)
29
+ events_with_correct_type << actual_event
30
+ end
31
+ end
32
+
33
+ expectations = expected_message(expected, expected_event, stream_name)
34
+
35
+ if expected.specified_count?
36
+ if correct_event_count >= 1
37
+ reality = failure_message_incorrect_count(expected_event, events_with_correct_type, correct_event_count)
38
+ elsif !events_with_correct_type.empty?
39
+ reality = failure_message_correct_type_incorrect_payload(expected_event, events_with_correct_type)
40
+ else
41
+ reality = failure_message_incorrect_type
42
+ end
43
+ else
44
+ if correct_event_count >= 1
45
+ next
46
+ else
47
+ if !events_with_correct_type.empty?
48
+ reality = failure_message_correct_type_incorrect_payload(expected_event, events_with_correct_type)
49
+ else
50
+ reality = failure_message_incorrect_type
51
+ end
52
+ end
53
+ end
54
+
55
+ return expectations + reality
56
+ end
57
+ end
58
+
59
+ def failure_message_when_negated(expected, events, _stream_name = nil)
60
+ return failure_message_when_negated_no_events if expected.empty?
61
+ if expected.specified_count?
62
+ <<~EOS
63
+ expected
64
+ #{expected.events.first.description}
65
+ not to be #{lingo.published} exactly #{expected.count} times
66
+
67
+ #{actual_events_list(events)}
68
+ EOS
69
+ else
70
+ <<~EOS
71
+ expected #{expected_events_list(expected.events)} not to #{"exactly " if expected.strict?}be #{lingo.published}
72
+
73
+ #{actual_events_list(events)}
74
+ EOS
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ attr_reader :differ, :lingo
81
+
82
+ def failure_message_no_events
83
+ "expected anything to be #{lingo.published}\n"
84
+ end
85
+
86
+ def failure_message_when_negated_no_events
87
+ "expected something to be #{lingo.published}\n"
88
+ end
89
+
90
+ def failure_message_incorrect_count(expected_event, events_with_correct_type, correct_event_count)
91
+ [
92
+ <<~EOS,
93
+
94
+ but was #{lingo.published} #{correct_event_count} times
95
+ EOS
96
+ if !events_with_correct_type.empty?
97
+ [
98
+ <<~EOS.strip,
99
+ There are events of correct type but with incorrect payload:
100
+ EOS
101
+ events_with_correct_type.each_with_index.map do |event_with_correct_type, index|
102
+ event_diff(expected_event, event_with_correct_type, index)
103
+ end,
104
+ nil
105
+ ]
106
+ end
107
+ ].compact.join("\n")
108
+ end
109
+
110
+ def failure_message_correct_type_incorrect_payload(expected_event, events_with_correct_type)
111
+ <<~EOS
112
+ , but it was not #{lingo.published}
113
+
114
+ There are events of correct type but with incorrect payload:
115
+ #{events_with_correct_type.each_with_index.map { |event_with_correct_type, index| event_diff(expected_event, event_with_correct_type, index) }.join("\n")}
116
+ EOS
117
+ end
118
+
119
+ def event_diff(expected_event, event_with_correct_type, index)
120
+ [
121
+ "#{index + 1}) #{event_with_correct_type.inspect}",
122
+ indent(data_diff(expected_event, event_with_correct_type), 4),
123
+ indent(metadata_diff(expected_event, event_with_correct_type), 4)
124
+ ].reject(&:empty?)
125
+ end
126
+
127
+ def indent(str, count)
128
+ str.to_s.split("\n").map { |l| l.sub(//, " " * count) }
129
+ end
130
+
131
+ def failure_message_incorrect_type
132
+ <<~EOS
133
+ , but there is no event with such type
134
+ EOS
135
+ end
136
+
137
+ def failure_message_strict(expected, events)
138
+ if expected.specified_count?
139
+ <<~EOS
140
+ expected only
141
+ #{expected.events.first.description}
142
+ to be #{lingo.published} #{expected.count} times
143
+
144
+ #{actual_events_list(events)}
145
+ EOS
146
+ else
147
+ <<~EOS
148
+ expected only #{expected_events_list(expected.events)} to be #{lingo.published}
149
+
150
+ #{actual_events_list(events)}
151
+ EOS
152
+ end
153
+ end
154
+
155
+ def data_diff(expected_event, event_with_correct_type)
156
+ if !expected_event.expected_data.nil?
157
+ "data diff:#{differ.diff(expected_event.expected_data, event_with_correct_type.data)}"
158
+ end
159
+ end
160
+
161
+ def metadata_diff(expected_event, event_with_correct_type)
162
+ if !expected_event.expected_metadata.nil?
163
+ "metadata diff:#{differ.diff(expected_event.expected_metadata, event_with_correct_type.metadata.to_h)}"
164
+ end
165
+ end
166
+
167
+ def expected_message(expected, expected_event, stream_name)
168
+ expected_stream = " in stream #{stream_name}" if stream_name
169
+ if expected.specified_count?
170
+ <<~EOS
171
+ expected event
172
+ #{expected_event.description}
173
+ to be #{lingo.published} #{expected.count} times#{expected_stream}
174
+ EOS
175
+ else
176
+ <<~EOS
177
+ expected #{expected_events_list(expected.events)} to be #{lingo.published}#{expected_stream}
178
+
179
+ i.e. expected event
180
+ #{expected_event.description}
181
+ to be #{lingo.published}
182
+ EOS
183
+ end.strip
184
+ end
185
+
186
+ def expected_events_list(expected)
187
+ <<~EOS.strip
188
+ [
189
+ #{expected.map(&:description).map { |d| indent(d, 2) }.join("\n")}
190
+ ]
191
+ EOS
192
+ end
193
+
194
+ def actual_events_list(actual)
195
+ <<~EOS.strip
196
+ but the following was #{lingo.published}: [
197
+ #{actual.map(&:inspect).map { |d| indent(d, 2) }.join("\n")}
198
+ ]
199
+ EOS
200
+ end
201
+ end
202
+
203
+ def have_published(differ)
204
+ HavePublished.new(differ, Lingo.new("published"))
205
+ end
206
+
207
+ def publish(differ)
208
+ HavePublished.new(differ, Lingo.new("published"))
209
+ end
210
+
211
+ def have_applied(differ)
212
+ HavePublished.new(differ, Lingo.new("applied"))
213
+ end
214
+
215
+ def apply(differ)
216
+ HavePublished.new(differ, Lingo.new("applied"))
217
+ end
218
+ end
219
+ end
220
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  module RSpec
5
- VERSION = "2.1.0"
5
+ VERSION = "2.4.0"
6
6
  end
7
7
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rspec'
3
+ require "rspec"
4
4
 
5
5
  module RubyEventStore
6
6
  module RSpec
@@ -8,15 +8,31 @@ module RubyEventStore
8
8
  end
9
9
  end
10
10
 
11
- require "ruby_event_store/rspec/version"
12
- require "ruby_event_store/rspec/be_event"
13
- require "ruby_event_store/rspec/have_published"
14
- require "ruby_event_store/rspec/have_applied"
15
- require "ruby_event_store/rspec/have_subscribed_to_events"
16
- require "ruby_event_store/rspec/publish"
17
- require "ruby_event_store/rspec/apply"
18
- require "ruby_event_store/rspec/matchers"
11
+ require_relative "rspec/version"
12
+ require_relative "rspec/be_event"
13
+ require_relative "rspec/expected_collection"
14
+ require_relative "rspec/fetch_events"
15
+ require_relative "rspec/fetch_unpublished_events"
16
+ require_relative "rspec/match_events"
17
+ require_relative "rspec/have_published"
18
+ require_relative "rspec/have_applied"
19
+ require_relative "rspec/have_subscribed_to_events"
20
+ require_relative "rspec/publish"
21
+ require_relative "rspec/apply"
22
+ require_relative "rspec/crude_failure_message_formatter"
23
+ require_relative "rspec/step_by_step_failure_message_formatter"
24
+ require_relative "rspec/matchers"
19
25
 
20
- ::RSpec.configure do |config|
21
- config.include ::RubyEventStore::RSpec::Matchers
26
+ module RubyEventStore
27
+ module RSpec
28
+ def self.default_formatter=(new_formatter)
29
+ @@default_formatter = new_formatter
30
+ end
31
+
32
+ def self.default_formatter
33
+ @@default_formatter ||= CrudeFailureMessageFormatter.new
34
+ end
35
+ end
22
36
  end
37
+
38
+ ::RSpec.configure { |config| config.include ::RubyEventStore::RSpec::Matchers }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_event_store-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkency
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-07 00:00:00.000000000 Z
11
+ date: 2022-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -35,11 +35,17 @@ files:
35
35
  - lib/ruby_event_store/rspec.rb
36
36
  - lib/ruby_event_store/rspec/apply.rb
37
37
  - lib/ruby_event_store/rspec/be_event.rb
38
+ - lib/ruby_event_store/rspec/crude_failure_message_formatter.rb
39
+ - lib/ruby_event_store/rspec/expected_collection.rb
40
+ - lib/ruby_event_store/rspec/fetch_events.rb
41
+ - lib/ruby_event_store/rspec/fetch_unpublished_events.rb
38
42
  - lib/ruby_event_store/rspec/have_applied.rb
39
43
  - lib/ruby_event_store/rspec/have_published.rb
40
44
  - lib/ruby_event_store/rspec/have_subscribed_to_events.rb
45
+ - lib/ruby_event_store/rspec/match_events.rb
41
46
  - lib/ruby_event_store/rspec/matchers.rb
42
47
  - lib/ruby_event_store/rspec/publish.rb
48
+ - lib/ruby_event_store/rspec/step_by_step_failure_message_formatter.rb
43
49
  - lib/ruby_event_store/rspec/version.rb
44
50
  homepage: https://railseventstore.org
45
51
  licenses:
@@ -49,6 +55,7 @@ metadata:
49
55
  changelog_uri: https://github.com/RailsEventStore/rails_event_store/releases
50
56
  source_code_uri: https://github.com/RailsEventStore/rails_event_store
51
57
  bug_tracker_uri: https://github.com/RailsEventStore/rails_event_store/issues
58
+ rubygems_mfa_required: 'true'
52
59
  post_install_message:
53
60
  rdoc_options: []
54
61
  require_paths:
@@ -57,14 +64,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
64
  requirements:
58
65
  - - ">="
59
66
  - !ruby/object:Gem::Version
60
- version: '2.5'
67
+ version: '2.7'
61
68
  required_rubygems_version: !ruby/object:Gem::Requirement
62
69
  requirements:
63
70
  - - ">="
64
71
  - !ruby/object:Gem::Version
65
72
  version: '0'
66
73
  requirements: []
67
- rubygems_version: 3.1.4
74
+ rubygems_version: 3.3.7
68
75
  signing_key:
69
76
  specification_version: 4
70
77
  summary: RSpec matchers for RubyEventStore