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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +14 -5
- data/lib/rspec/activerecord/expectations/matchers/query_count_matcher.rb +30 -68
- data/lib/rspec/activerecord/expectations/matchers/transaction_matcher.rb +71 -42
- data/lib/rspec/activerecord/expectations/message_builder.rb +160 -0
- data/lib/rspec/activerecord/expectations.rb +1 -0
- data/rspec-activerecord-expectations.gemspec +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f84a252fb5e5923d4bc6c03ed4d99ede76579017d8391dbf6f48a665236e54e
|
4
|
+
data.tar.gz: ce944950ee62940f67f79204966f5954fc28eac8f52dee79e18475933d9f3910
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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 :
|
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
|
-
@
|
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
|
-
#
|
38
|
+
# QUANTIFIERS
|
30
39
|
|
31
40
|
def less_than(n)
|
32
|
-
@
|
33
|
-
@
|
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
|
-
@
|
40
|
-
@
|
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
|
-
@
|
47
|
-
@
|
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
|
-
@
|
54
|
-
@
|
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
|
-
@
|
61
|
-
@
|
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
|
-
#
|
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
|
-
|
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
|
-
@
|
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
|
-
|
17
|
-
@count > 0
|
33
|
+
result
|
18
34
|
end
|
19
35
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
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
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
89
|
+
def times
|
90
|
+
self # NOOP
|
54
91
|
end
|
92
|
+
alias_method :time, :times
|
55
93
|
|
56
|
-
def
|
57
|
-
|
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'
|
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.
|
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-
|
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
|