ruby_event_store-rspec 2.1.0 → 2.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1362df53ba19bf0a2194492d627ab17cd22ee658c9fefa47b5c61c0b8eec562
4
- data.tar.gz: c665c377cf751768cb1ec6abd8f486231ae382c6dafaef36e8cf0aaf01bc3800
3
+ metadata.gz: 2824fc38cdb13ea93b60cc332617ae107281ca075102897cf3b3627b5455bd07
4
+ data.tar.gz: 2ad9ac851fb63a5707d679679a86aa294bc80754c054d4b1210005212cb395d9
5
5
  SHA512:
6
- metadata.gz: 94e802c2bbed41e35aa8f8a2507cc127afe2e853e2266d60a56ea240f98c09f357bf7c664e7489de95a3f69d0b4892cd7f50a1c261fe2e7c995b531b45518fe4
7
- data.tar.gz: bf4cc3ec24389daf273187565731d7dc460e2d2ba9621605dafbbce46b19df9324f27cc98deb8c749955e15b10a3c6dd8167f36ae07fbea0592a2c2b8e884261
6
+ metadata.gz: 6756a69b71fb2c42cb484988a85f365134026f7fc8a4f9dde87cbac934d9190234a2788790fb89a6f9588e4b3ff9aa4b799ca893524c8614482aa76ec592dc5c
7
+ data.tar.gz: 5f9c80aff6dffe7c16ec7da698e7f1d84af2554121d51b0bcefae0ff113fc785cf97162d811441a7e29ba375ad4fee208c263c2ba9ac6b8c56d5711189f896cd
@@ -8,14 +8,32 @@ 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"
25
+
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
36
+ end
19
37
 
20
38
  ::RSpec.configure do |config|
21
39
  config.include ::RubyEventStore::RSpec::Matchers
@@ -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
@@ -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,20 +3,21 @@
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
6
+ def initialize(*expected, phraser:, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
8
+ @failure_message_formatter = failure_message_formatter
10
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
 
@@ -26,38 +27,30 @@ module RubyEventStore
26
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,28 +3,35 @@
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
6
+ def initialize(*expected, phraser:, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
10
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
 
@@ -34,42 +41,39 @@ module RubyEventStore
34
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
@@ -26,12 +26,12 @@ module RubyEventStore
26
26
 
27
27
  def failure_message
28
28
  "expected #{handler} to be subscribed to events, diff:" +
29
- differ.diff_as_string(expected.to_s, subscribed_to.to_s)
29
+ differ.diff(expected.to_s + "\n", subscribed_to)
30
30
  end
31
31
 
32
32
  def failure_message_when_negated
33
33
  "expected #{handler} not to be subscribed to events, diff:" +
34
- differ.diff_as_string(expected.to_s, subscribed_to.to_s)
34
+ differ.diff(expected.to_s + "\n", subscribed_to)
35
35
  end
36
36
 
37
37
  def description
@@ -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
@@ -39,11 +39,11 @@ module RubyEventStore
39
39
  alias :event :be_an_event
40
40
 
41
41
  def have_published(*expected)
42
- HavePublished.new(*expected, differ: differ, phraser: phraser)
42
+ HavePublished.new(*expected, phraser: phraser, failure_message_formatter: RSpec.default_formatter.have_published(differ))
43
43
  end
44
44
 
45
45
  def have_applied(*expected)
46
- HaveApplied.new(*expected, differ: differ, phraser: phraser)
46
+ HaveApplied.new(*expected, phraser: phraser, failure_message_formatter: RSpec.default_formatter.have_applied(differ))
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,218 @@
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
+ attr_reader :differ, :lingo
80
+
81
+ def failure_message_no_events
82
+ "expected anything to be #{lingo.published}\n"
83
+ end
84
+
85
+ def failure_message_when_negated_no_events
86
+ "expected something to be #{lingo.published}\n"
87
+ end
88
+
89
+ def failure_message_incorrect_count(expected_event, events_with_correct_type, correct_event_count)
90
+ [
91
+ <<~EOS,
92
+
93
+ but was #{lingo.published} #{correct_event_count} times
94
+ EOS
95
+
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 {|event_with_correct_type, index| event_diff(expected_event, event_with_correct_type, index) },
102
+ nil
103
+ ]
104
+ end
105
+ ].compact.join("\n")
106
+ end
107
+
108
+ def failure_message_correct_type_incorrect_payload(expected_event, events_with_correct_type)
109
+ <<~EOS
110
+ , but it was not #{lingo.published}
111
+
112
+ There are events of correct type but with incorrect payload:
113
+ #{events_with_correct_type.each_with_index.map {|event_with_correct_type, index| event_diff(expected_event, event_with_correct_type, index) }.join("\n")}
114
+ EOS
115
+ end
116
+
117
+ def event_diff(expected_event, event_with_correct_type, index)
118
+ [
119
+ "#{index + 1}) #{event_with_correct_type.inspect}",
120
+ indent(data_diff(expected_event, event_with_correct_type), 4),
121
+ indent(metadata_diff(expected_event, event_with_correct_type), 4),
122
+ ].reject(&:empty?)
123
+ end
124
+
125
+ def indent(str, count)
126
+ str.to_s.split("\n").map {|l| l.sub(//, " " * count) }
127
+ end
128
+
129
+ def failure_message_incorrect_type
130
+ <<~EOS
131
+ , but there is no event with such type
132
+ EOS
133
+ end
134
+
135
+ def failure_message_strict(expected, events)
136
+ if expected.specified_count?
137
+ <<~EOS
138
+ expected only
139
+ #{expected.events.first.description}
140
+ to be #{lingo.published} #{expected.count} times
141
+
142
+ #{actual_events_list(events)}
143
+ EOS
144
+ else
145
+ <<~EOS
146
+ expected only #{expected_events_list(expected.events)} to be #{lingo.published}
147
+
148
+ #{actual_events_list(events)}
149
+ EOS
150
+ end
151
+ end
152
+
153
+ def data_diff(expected_event, event_with_correct_type)
154
+ if !expected_event.expected_data.nil?
155
+ "data diff:#{differ.diff(expected_event.expected_data, event_with_correct_type.data)}"
156
+ end
157
+ end
158
+
159
+ def metadata_diff(expected_event, event_with_correct_type)
160
+ if !expected_event.expected_metadata.nil?
161
+ "metadata diff:#{differ.diff(expected_event.expected_metadata, event_with_correct_type.metadata.to_h)}"
162
+ end
163
+ end
164
+
165
+ def expected_message(expected, expected_event, stream_name)
166
+ expected_stream = " in stream #{stream_name}" if stream_name
167
+ if expected.specified_count?
168
+ <<~EOS
169
+ expected event
170
+ #{expected_event.description}
171
+ to be #{lingo.published} #{expected.count} times#{expected_stream}
172
+ EOS
173
+ else
174
+ <<~EOS
175
+ expected #{expected_events_list(expected.events)} to be #{lingo.published}#{expected_stream}
176
+
177
+ i.e. expected event
178
+ #{expected_event.description}
179
+ to be #{lingo.published}
180
+ EOS
181
+ end.strip
182
+ end
183
+
184
+ def expected_events_list(expected)
185
+ <<~EOS.strip
186
+ [
187
+ #{expected.map(&:description).map {|d| indent(d, 2) }.join("\n")}
188
+ ]
189
+ EOS
190
+ end
191
+
192
+ def actual_events_list(actual)
193
+ <<~EOS.strip
194
+ but the following was #{lingo.published}: [
195
+ #{actual.map(&:inspect).map {|d| indent(d, 2) }.join("\n")}
196
+ ]
197
+ EOS
198
+ end
199
+ end
200
+
201
+ def have_published(differ)
202
+ HavePublished.new(differ, Lingo.new("published"))
203
+ end
204
+
205
+ def publish(differ)
206
+ HavePublished.new(differ, Lingo.new("published"))
207
+ end
208
+
209
+ def have_applied(differ)
210
+ HavePublished.new(differ, Lingo.new("applied"))
211
+ end
212
+
213
+ def apply(differ)
214
+ HavePublished.new(differ, Lingo.new("applied"))
215
+ end
216
+ end
217
+ end
218
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  module RSpec
5
- VERSION = "2.1.0"
5
+ VERSION = "2.2.0"
6
6
  end
7
7
  end
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.2.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: 2021-04-09 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:
@@ -57,7 +63,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
63
  requirements:
58
64
  - - ">="
59
65
  - !ruby/object:Gem::Version
60
- version: '2.5'
66
+ version: '2.6'
61
67
  required_rubygems_version: !ruby/object:Gem::Requirement
62
68
  requirements:
63
69
  - - ">="