rspec-activerecord-expectations 2.2.0 → 2.3.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: 3205b050d5f2164b5bc72de9fe29a0f6d988cd869ecd6703a09b83b2550bb02e
4
- data.tar.gz: d43d363888f5c8f85dc551fae7d1381a945ac304c597192544ffd1506d1f7495
3
+ metadata.gz: 0f84a252fb5e5923d4bc6c03ed4d99ede76579017d8391dbf6f48a665236e54e
4
+ data.tar.gz: ce944950ee62940f67f79204966f5954fc28eac8f52dee79e18475933d9f3910
5
5
  SHA512:
6
- metadata.gz: a98cfac5ba127c3e5f7bd2b42ef48fa56e6750e75d0ab841988b076a2a39c0ca13e5d67e534020fd81af3b7885cba3d192c23a2fbb0b4ccd0d8e725e3cafb147
7
- data.tar.gz: 65347df27d406531b1ea594bfe9a3d9b6405c75ad5f8be5c3f52f668e3d976e0e73c5e1560caeb308cb203b73547ab18c0ffd758aff0e913a9f010665a0a6595
6
+ metadata.gz: 4828d15b2420ae821199db596b96cc5d4630e3994b6c12bb2d6b95c32067388bb30161b318b0c03c49771d5b95e3010757e564c70a1f6840ab6934c14479f58d
7
+ data.tar.gz: d8220ae71a3e1dc6be694f976185262e019d55c6eedc1ec255492fbb5cd7a2988ac45e3519c14f1936cf8d0fd09062710f71b2d788322771e4ccde864be436a9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.3.0] - 2022-01-29
4
+ - Add quantifiers to transaction matchers
5
+ - Add much more complicated English output for readable matchers
6
+ - Update README for the same
7
+
3
8
  ## [2.2.0] - 2022-01-14
4
9
  - Adds transaction matcher to verify that code was executed within a
5
10
  transaction at minimum
data/README.md CHANGED
@@ -230,8 +230,20 @@ end.not_to rollback_a_transaction
230
230
  ```
231
231
 
232
232
  Note that ActiveRecord will not only roll back the transaction, but also
233
- re-raise errors. As such, it's necessary in this example to rescue that
234
- error in order for the test to fail.
233
+ re-raise errors. As such, it's necessary in this example to rescue that error,
234
+ otherwise the test would fail simply because the code caused a `raise`.
235
+
236
+ ### Counting Transactions
237
+
238
+ Similar to counting queries, you can quantify the number of transactions you
239
+ expect to succeed / fail. This is probably of limited value in all but some
240
+ very specific cases.
241
+
242
+ ```ruby
243
+ expect {}.to commit_a_transaction.once
244
+ expect {}.to rollback_a_transaction.exactly(5).times
245
+ expect {}.to commit_a_transaction.at_least(5).times
246
+ ```
235
247
 
236
248
  ## Future Planned Functionality
237
249
 
@@ -248,9 +260,6 @@ expect {}.to create.exactly(5).of_type(User)
248
260
  expect {}.to insert.exactly(5).subscription_changes
249
261
  expect {}.to update.exactly(2).of_any_type
250
262
  expect {}.to delete.exactly(2).of_any_type
251
-
252
- expect {}.to commit_a_transaction.once
253
- expect {}.to rollback_a_transaction.exactly(5).times
254
263
  ```
255
264
 
256
265
  - warn if we smite any built in methods (or methods from other libs)
@@ -1,16 +1,25 @@
1
1
  module RSpec::ActiveRecord::Expectations
2
2
  module Matchers
3
3
  class QueryCountMatcher
4
- attr_reader :failure_message, :failure_message_when_negated
4
+ attr_reader :collector, :quantifier, :comparison, :query_type
5
5
 
6
6
  def initialize
7
7
  @collector = Collector.new
8
+ @message_builder = MessageBuilder.new(self)
8
9
 
9
10
  @match_method = nil
10
- @comparison = nil
11
+ @quantifier = nil
11
12
  @query_type = nil
12
13
  end
13
14
 
15
+ def failure_message
16
+ @message_builder.failure_message
17
+ end
18
+
19
+ def failure_message_when_negated
20
+ @message_builder.failure_message_when_negated
21
+ end
22
+
14
23
  def supports_block_expectations?
15
24
  true
16
25
  end
@@ -26,39 +35,44 @@ module RSpec::ActiveRecord::Expectations
26
35
  result
27
36
  end
28
37
 
29
- # COMPARISON TYPES
38
+ # QUANTIFIERS
30
39
 
31
40
  def less_than(n)
32
- @comparison = n
33
- @match_method = method(:compare_less_than)
41
+ @quantifier = n
42
+ @comparison = :less_than
43
+ @match_method = -> { actual_count < @quantifier }
34
44
  self
35
45
  end
36
46
  alias_method :fewer_than, :less_than
37
47
 
38
48
  def less_than_or_equal_to(n)
39
- @comparison = n
40
- @match_method = method(:compare_less_than_or_equal_to)
49
+ @quantifier = n
50
+ @comparison = :less_than_or_equal_to
51
+ @match_method = -> { actual_count <= @quantifier }
41
52
  self
42
53
  end
43
54
  alias_method :at_most, :less_than_or_equal_to
44
55
 
45
56
  def greater_than(n)
46
- @comparison = n
47
- @match_method = method(:compare_greater_than)
57
+ @quantifier = n
58
+ @comparison = :greater_than
59
+ @match_method = -> { actual_count > @quantifier }
48
60
  self
49
61
  end
50
62
  alias_method :more_than, :greater_than
51
63
 
52
64
  def greater_than_or_equal_to(n)
53
- @comparison = n
54
- @match_method = method(:compare_greater_than_or_equal_to)
65
+ @quantifier = n
66
+ @comparison = :greater_than_or_equal_to
67
+ @match_method = -> { actual_count >= @quantifier }
55
68
  self
56
69
  end
57
70
  alias_method :at_least, :greater_than_or_equal_to
58
71
 
59
72
  def exactly(n)
60
- @comparison = n
61
- @match_method = method(:compare_exactly)
73
+ @quantifier = n
74
+ @comparison = :exactly
75
+ @match_method = -> { actual_count == @quantifier }
62
76
  self
63
77
  end
64
78
 
@@ -79,62 +93,10 @@ module RSpec::ActiveRecord::Expectations
79
93
  end
80
94
  end
81
95
 
82
- # TODO singularize everything
83
- alias_method :query, :queries
84
-
85
- private
86
-
87
- def humanized_query_type
88
- @query_type.to_s.gsub("_", " ")
89
- end
90
-
91
- # MATCHERS
92
-
93
- def compare_less_than
94
- count = @collector.queries_of_type(@query_type)
95
-
96
- @failure_message = "expected block to execute fewer than #{@comparison} #{humanized_query_type}, but it executed #{count}"
97
- @failure_message_when_negated = "expected block not to execute fewer than #{@comparison} #{humanized_query_type}, but it executed #{count}"
98
-
99
- count < @comparison
100
- end
101
-
102
- def compare_less_than_or_equal_to
103
- count = @collector.queries_of_type(@query_type)
104
-
105
- # boy this negated operator is confusing. don't use that plz.
106
- @failure_message = "expected block to execute at most #{@comparison} #{humanized_query_type}, but it executed #{count}"
107
- @failure_message_when_negated = "expected block not to execute any less than #{@comparison} #{humanized_query_type}, but it executed #{count}"
108
-
109
- count <= @comparison
110
- end
111
-
112
- def compare_greater_than
113
- count = @collector.queries_of_type(@query_type)
114
-
115
- @failure_message = "expected block to execute greater than #{@comparison} #{humanized_query_type}, but it executed #{count}"
116
- @failure_message_when_negated = "expected block not to execute greater than #{@comparison} #{humanized_query_type}, but it executed #{count}"
117
-
118
- count > @comparison
119
- end
120
-
121
- def compare_greater_than_or_equal_to
122
- count = @collector.queries_of_type(@query_type)
123
-
124
- # see above, negating this matcher is so confusing.
125
- @failure_message = "expected block to execute at least #{@comparison} #{humanized_query_type}, but it executed #{count}"
126
- @failure_message_when_negated = "expected block not to execute any more than #{@comparison} #{humanized_query_type}, but it executed #{count}"
127
-
128
- count >= @comparison
129
- end
130
-
131
- def compare_exactly
132
- count = @collector.queries_of_type(@query_type)
133
-
134
- @failure_message = "expected block to execute exactly #{@comparison} #{humanized_query_type}, but it executed #{count}"
135
- @failure_message_when_negated = "expected block not to execute exactly #{@comparison} #{humanized_query_type}, but it executed #{count}"
96
+ # helper for message builder
136
97
 
137
- count == @comparison
98
+ def actual_count
99
+ @collector.queries_of_type(@query_type)
138
100
  end
139
101
  end
140
102
  end
@@ -1,9 +1,22 @@
1
1
  module RSpec::ActiveRecord::Expectations
2
2
  module Matchers
3
3
  class TransactionMatcher
4
+ attr_reader :collector, :quantifier, :comparison, :query_type
5
+
4
6
  def initialize(transaction_type)
5
7
  @collector = Collector.new
6
- @transaction = transaction_type
8
+ @query_type = transaction_type
9
+ @message_builder = MessageBuilder.new(self)
10
+
11
+ self.at_least(1)
12
+ end
13
+
14
+ def failure_message
15
+ @message_builder.failure_message
16
+ end
17
+
18
+ def failure_message_when_negated
19
+ @message_builder.failure_message_when_negated
7
20
  end
8
21
 
9
22
  def supports_block_expectations?
@@ -11,59 +24,75 @@ module RSpec::ActiveRecord::Expectations
11
24
  end
12
25
 
13
26
  def matches?(block)
27
+ raise NoComparisonError unless @match_method
28
+
14
29
  block.call
30
+ result = @match_method.call
31
+ @collector.finalize
15
32
 
16
- @count = @collector.queries_of_type(@transaction)
17
- @count > 0
33
+ result
18
34
  end
19
35
 
20
- def failure_message
21
- type_msg = case @transaction
22
- when :transaction_queries
23
- "execute a transaction"
24
- when :rollback_queries
25
- "roll back a transaction"
26
- when :commit_queries
27
- "commit a transaction"
28
- end
29
-
30
- "expected block to #{type_msg}, but it didn't do so"
36
+ # QUANTIFIERS
37
+
38
+ def less_than(n)
39
+ @quantifier = n
40
+ @comparison = :less_than
41
+ @match_method = -> { actual_count < @quantifier }
42
+ self
31
43
  end
44
+ alias_method :fewer_than, :less_than
32
45
 
33
- def failure_message_when_negated
34
- if @count == 1
35
- negated_message_singular
36
- else
37
- negated_message_plural
38
- end
46
+ def less_than_or_equal_to(n)
47
+ @quantifier = n
48
+ @comparison = :less_than_or_equal_to
49
+ @match_method = -> { actual_count <= @quantifier }
50
+ self
51
+ end
52
+ alias_method :at_most, :less_than_or_equal_to
53
+
54
+ def greater_than(n)
55
+ @quantifier = n
56
+ @comparison = :greater_than
57
+ @match_method = -> { actual_count > @quantifier }
58
+ self
39
59
  end
60
+ alias_method :more_than, :greater_than
40
61
 
41
- private
62
+ def greater_than_or_equal_to(n)
63
+ @quantifier = n
64
+ @comparison = :greater_than_or_equal_to
65
+ @match_method = -> { actual_count >= @quantifier }
66
+ self
67
+ end
68
+ alias_method :at_least, :greater_than_or_equal_to
69
+
70
+ def exactly(n)
71
+ @quantifier = n
72
+ @comparison = :exactly
73
+ @match_method = -> { actual_count == @quantifier }
74
+ self
75
+ end
42
76
 
43
- def negated_message_singular
44
- pre_msg, post_msg = case @transaction
45
- when :transaction_queries
46
- ["execute a transaction", "executed one"]
47
- when :rollback_queries
48
- ["roll back a transaction", "rolled one back"]
49
- when :commit_queries
50
- ["commit a transaction", "committed one"]
51
- end
77
+ def once
78
+ exactly(1).time
79
+ end
80
+
81
+ def twice
82
+ exactly(2).times
83
+ end
84
+
85
+ def thrice # hehe
86
+ exactly(3).times
87
+ end
52
88
 
53
- "expected block not to #{pre_msg}, but it #{post_msg}"
89
+ def times
90
+ self # NOOP
54
91
  end
92
+ alias_method :time, :times
55
93
 
56
- def negated_message_plural
57
- pre_msg, post_msg = case @transaction
58
- when :transaction_queries
59
- ["execute a transaction", "executed #{@count} transactions"]
60
- when :rollback_queries
61
- ["roll back a transaction", "rolled back #{@count} transactions"]
62
- when :commit_queries
63
- ["commit a transaction", "committed #{@count} transactions"]
64
- end
65
-
66
- "expected block not to #{pre_msg}, but it #{post_msg}"
94
+ def actual_count
95
+ @collector.queries_of_type(@query_type)
67
96
  end
68
97
  end
69
98
  end
@@ -0,0 +1,160 @@
1
+ module RSpec::ActiveRecord::Expectations
2
+ class MessageBuilder
3
+ attr_reader :matcher, :phrase_builder
4
+
5
+ def initialize(matcher)
6
+ @matcher = matcher
7
+ @phrase_builder = case matcher
8
+ when Matchers::QueryCountMatcher then QueryPhrases.new(matcher)
9
+ when Matchers::TransactionMatcher then TransactionPhrases.new(matcher)
10
+ else raise ArgumentError
11
+ end
12
+ end
13
+
14
+ def failure_message
15
+ "expected block to #{phrase_builder.prefix}, but it #{phrase_builder.suffix}"
16
+ end
17
+
18
+ def failure_message_when_negated
19
+ "expected block not to #{phrase_builder.prefix}, but it #{phrase_builder.negative_suffix}"
20
+ end
21
+
22
+ private
23
+
24
+ # TODO: expect {}.not_to execute.less_than_or_equal_to(3).insert_queries
25
+ # expected block not to execute at most 3 insert queries, but it executed 0
26
+
27
+
28
+ class QueryPhrases
29
+ attr_reader :matcher
30
+
31
+ def initialize(matcher)
32
+ @matcher = matcher
33
+ end
34
+
35
+ def prefix
36
+ "execute #{comparison_phrase} #{query_type_name}"
37
+ end
38
+
39
+ def suffix
40
+ if matcher.actual_count == 0
41
+ "didn't execute any"
42
+ elsif matcher.actual_count == 1
43
+ "executed one"
44
+ else
45
+ "executed #{matcher.actual_count}"
46
+ end
47
+ end
48
+
49
+ # using positive suffix from above can cause double negatives
50
+ def negative_suffix
51
+ if matcher.comparison == :exactly
52
+ "did so"
53
+ elsif matcher.actual_count == 1
54
+ "executed one"
55
+ else
56
+ "executed #{matcher.actual_count}"
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def query_type_name
63
+ if matcher.quantifier == 1
64
+ matcher.query_type.to_s.gsub("_", " ").gsub("queries", "query")
65
+ else
66
+ matcher.query_type.to_s.gsub("_", " ")
67
+ end
68
+ end
69
+
70
+ def comparison_phrase
71
+ quant = if matcher.quantifier == 1 && matcher.comparison == :exactly
72
+ "a"
73
+ elsif matcher.quantifier == 1
74
+ "one"
75
+ else
76
+ matcher.quantifier
77
+ end
78
+
79
+ case matcher.comparison
80
+ when :exactly then quant
81
+ when :greater_than then "more than #{quant}"
82
+ when :greater_than_or_equal_to then "at least #{quant}"
83
+ when :less_than then "less than #{quant}"
84
+ when :less_than_or_equal_to then "at most #{quant}"
85
+ else raise ArgumentError, "unsupported comparison matcher #{matcher.comparison}"
86
+ end
87
+ end
88
+ end
89
+
90
+ class TransactionPhrases
91
+ attr_reader :matcher
92
+
93
+ def initialize(matcher)
94
+ @matcher = matcher
95
+ end
96
+
97
+ def prefix
98
+ nouns = matcher.quantifier == 1 ? "transaction" : "transactions"
99
+ verb = case matcher.query_type
100
+ when :transaction_queries then "execute"
101
+ when :rollback_queries then "roll back"
102
+ when :commit_queries then "commit"
103
+ end
104
+
105
+ "#{verb} #{comparison} #{nouns}"
106
+ end
107
+
108
+ def suffix
109
+ singular_verb = case matcher.query_type
110
+ when :transaction_queries then "execute"
111
+ when :rollback_queries then "roll back"
112
+ when :commit_queries then "commit"
113
+ end
114
+
115
+ plural_verb = case matcher.query_type
116
+ when :transaction_queries then "executed"
117
+ when :rollback_queries then "rolled back"
118
+ when :commit_queries then "committed"
119
+ end
120
+
121
+ if matcher.actual_count == 0
122
+ "didn't #{singular_verb} any"
123
+ elsif matcher.actual_count == 1
124
+ "#{plural_verb} one"
125
+ else
126
+ "#{plural_verb} #{matcher.actual_count}"
127
+ end
128
+ end
129
+
130
+ def negative_suffix
131
+ if matcher.comparison == :exactly
132
+ "did so"
133
+ else
134
+ suffix
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def comparison
141
+ quant = if matcher.quantifier == 1 && matcher.comparison == :exactly
142
+ "a"
143
+ elsif matcher.quantifier == 1
144
+ "one"
145
+ else
146
+ matcher.quantifier
147
+ end
148
+
149
+ case matcher.comparison
150
+ when :exactly then quant
151
+ when :greater_than then "more than #{quant}"
152
+ when :greater_than_or_equal_to then "at least #{quant}"
153
+ when :less_than then "less than #{quant}"
154
+ when :less_than_or_equal_to then "at most #{quant}"
155
+ else raise ArgumentError, "unsupported comparison matcher #{matcher.comparison}"
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -31,6 +31,7 @@ end
31
31
  require_relative 'expectations/errors'
32
32
  require_relative 'expectations/query_inspector'
33
33
  require_relative 'expectations/collector'
34
+ require_relative 'expectations/message_builder'
34
35
  require_relative 'expectations/matchers/query_count_matcher'
35
36
  require_relative 'expectations/matchers/load_matcher'
36
37
  require_relative 'expectations/matchers/transaction_matcher'
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "rspec-activerecord-expectations"
3
- spec.version = '2.2.0'
3
+ spec.version = '2.3.0'
4
4
  spec.authors = ["Joseph Mastey"]
5
5
  spec.email = ["hello@joemastey.com"]
6
6
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-activerecord-expectations
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Mastey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-15 00:00:00.000000000 Z
11
+ date: 2022-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -105,6 +105,7 @@ files:
105
105
  - lib/rspec/activerecord/expectations/matchers/load_matcher.rb
106
106
  - lib/rspec/activerecord/expectations/matchers/query_count_matcher.rb
107
107
  - lib/rspec/activerecord/expectations/matchers/transaction_matcher.rb
108
+ - lib/rspec/activerecord/expectations/message_builder.rb
108
109
  - lib/rspec/activerecord/expectations/query_inspector.rb
109
110
  - rspec-activerecord-expectations.gemspec
110
111
  homepage: https://github.com/jmmastey/rspec-activerecord-expectations