rspec-activerecord-expectations 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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