rspec-activerecord-expectations 1.3.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: 92f14ca413e9e716ea9b3046abc13d3929893185cfef8cdeaf14b7bebc93755c
4
- data.tar.gz: 93bfa962b7954844560961d66d080fa2c45a3188b8c9b92420d1852e36ce718b
3
+ metadata.gz: 0f84a252fb5e5923d4bc6c03ed4d99ede76579017d8391dbf6f48a665236e54e
4
+ data.tar.gz: ce944950ee62940f67f79204966f5954fc28eac8f52dee79e18475933d9f3910
5
5
  SHA512:
6
- metadata.gz: bb81903bbb00357ab9613a59fb2684bfaad7671fbca955f9876d503f5b179f8ead6d36f248ea24909f30db3ddb6f4fc307c509a181d2fe85d33d1e04006856da
7
- data.tar.gz: f813c1e8d6e320f6810cc33620fe702c379f47124b6add4bc1bdabde81c4e12c60a6aac5711caf8c23672f838d6185106ffb3695770a7c4002d04404c87929cb
6
+ metadata.gz: 4828d15b2420ae821199db596b96cc5d4630e3994b6c12bb2d6b95c32067388bb30161b318b0c03c49771d5b95e3010757e564c70a1f6840ab6934c14479f58d
7
+ data.tar.gz: d8220ae71a3e1dc6be694f976185262e019d55c6eedc1ec255492fbb5cd7a2988ac45e3519c14f1936cf8d0fd09062710f71b2d788322771e4ccde864be436a9
@@ -11,19 +11,19 @@ jobs:
11
11
  strategy:
12
12
  matrix:
13
13
  os: [ubuntu-latest, macos-latest]
14
- ruby_version: ['3.0', '2.7']
14
+ ruby-version: ['3.1', '3.0', '2.7']
15
15
  rails: ['5.0', '5.1', '5.2', '6.0', '6.1', '7.0']
16
16
 
17
17
  runs-on: ${{ matrix.os }}
18
- name: ruby ${{ matrix.ruby_version }}, rails ${{ matrix.rails }}, ${{ matrix.os }}
18
+ name: ruby ${{ matrix.ruby-version }}, rails ${{ matrix.rails }}, ${{ matrix.os }}
19
19
 
20
20
  steps:
21
21
  - uses: actions/checkout@v2
22
22
 
23
- - name: Set up Ruby ${{ matrix.ruby_version }}
23
+ - name: Set up Ruby ${{ matrix.ruby-version }}
24
24
  uses: ruby/setup-ruby@v1
25
25
  with:
26
- ruby_version: ${{ matrix.ruby_version }}
26
+ ruby-version: ${{ matrix.ruby-version }}
27
27
  bundler-cache: true
28
28
 
29
29
  - name: Install Gems w/ Rails ${{ matrix.rails }}
@@ -40,19 +40,19 @@ jobs:
40
40
  test_legacy:
41
41
  strategy:
42
42
  matrix:
43
- ruby_version: ['2.6', '2.5', '2.4', '2.3']
43
+ ruby-version: ['2.6', '2.5']
44
44
  rails: ['5.0', '5.1', '5.2']
45
45
 
46
46
  runs-on: ubuntu-latest
47
- name: legacy ruby ${{ matrix.ruby_version }}, rails ${{ matrix.rails }}
47
+ name: ruby ${{ matrix.ruby-version }}, rails ${{ matrix.rails }}
48
48
 
49
49
  steps:
50
50
  - uses: actions/checkout@v2
51
51
 
52
- - name: Set up Ruby ${{ matrix.ruby_version }}
52
+ - name: Set up Ruby ${{ matrix.ruby-version }}
53
53
  uses: ruby/setup-ruby@v1
54
54
  with:
55
- ruby_version: ${{ matrix.ruby_version }}
55
+ ruby-version: ${{ matrix.ruby-version }}
56
56
  bundler-cache: true
57
57
 
58
58
  - name: Install Gems w/ Rails ${{ matrix.rails }}
@@ -65,3 +65,33 @@ jobs:
65
65
 
66
66
  - name: Run tests
67
67
  run: bundle exec rspec
68
+
69
+ test_alt_rubies:
70
+ strategy:
71
+ matrix:
72
+ ruby-version: ['jruby', 'truffleruby']
73
+ rails: ['6.0', '6.1'] # jruby / jdbc-sqlite3 end up tightly bound to rails 6.x
74
+
75
+ runs-on: ubuntu-latest
76
+ name: ${{ matrix.ruby-version }}, rails ${{ matrix.rails }}
77
+
78
+ steps:
79
+ - uses: actions/checkout@v2
80
+
81
+ - name: Set up Ruby ${{ matrix.ruby-version }}
82
+ uses: ruby/setup-ruby@v1
83
+ with:
84
+ ruby-version: ${{ matrix.ruby-version }}
85
+ bundler-cache: true
86
+
87
+ - name: Install Gems w/ Rails ${{ matrix.rails }}
88
+ env:
89
+ MATRIX_RAILS_VERSION: ${{ matrix.rails }}
90
+ run: |
91
+ export BUNDLE_GEMFILE="${GITHUB_WORKSPACE}/gemfiles/rails_${MATRIX_RAILS_VERSION}.gemfile"
92
+ gem install bundler
93
+ bundle install --jobs 4 --retry 3
94
+
95
+ - name: Run tests
96
+ run: bundle exec rspec
97
+
data/CHANGELOG.md CHANGED
@@ -1,25 +1,48 @@
1
1
  # Changelog
2
2
 
3
- ## [1.3.0] - 2020-12-31
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
+
8
+ ## [2.2.0] - 2022-01-14
9
+ - Adds transaction matcher to verify that code was executed within a
10
+ transaction at minimum
11
+ - Transaction matcher also allows for commits and rollbacks
12
+ - Update README for accompanying functionality
13
+
14
+ ## [2.1.1] - 2022-01-12
15
+ - Gemspec is really generated using the version of ruby that's locally in use.
16
+ Update that build artifact to use non-java dependencies.
17
+
18
+ ## [2.1.0] - 2022-01-12
19
+ - Change CI matrix for more version compatibility
20
+ - Update gemspec to move some deps into development only
21
+ - README changes
22
+
23
+ ## [2.0.0] - 2022-01-07
24
+ - Require much more recent ruby, to use syntax that's been around since 2017
25
+
26
+ ## [1.3.0] - 2021-12-31
4
27
  - Add `repeatedly_load` matcher
5
28
  - Add query type matchers for `load_queries`, `schema_queries`, `transaction_queries`, `destroy_queries`
6
29
  - Allow singular version of all query types (e.g. `transaction_queries` vs `transaction_query`)
7
30
  - Fix failure message for `execute.exactly` matcher
8
31
 
9
- ## [1.2.0] - 2020-12-31
32
+ ## [1.2.0] - 2021-12-31
10
33
  - Add `query` as a synonym for `queries`
11
34
  - Ignore schema and transaction queries in query count
12
35
  - Add beginning of recording specific query types
13
36
  - Add query count matcher for `exactly`
14
37
 
15
- ## [1.1.0] - 2020-12-30
38
+ ## [1.1.0] - 2021-12-30
16
39
  - Add query count matchers for e.g. `less_than_or_equal_to`, `greater_than`
17
40
 
18
- ## [1.0.1] - 2020-12-30
41
+ ## [1.0.1] - 2021-12-30
19
42
  - Pin all the dependencies to a proper working subset
20
43
  - Expand testing to many rails / ruby combinations
21
44
 
22
- ## [1.0.0] - 2020-12-30
45
+ ## [1.0.0] - 2021-12-30
23
46
  - Basic gem w/ all the fixins and README and such
24
47
  - Add `less_than` comparison, and `queries` type
25
48
  - Basic tests in place, and not bad tbh
data/README.md CHANGED
@@ -30,7 +30,8 @@ group 'test' do
30
30
  end
31
31
  ```
32
32
 
33
- Then, include the functionality within your spec helper (or in a support file).
33
+ Then, include the functionality within your `rails_helper.rb` (or in a support
34
+ file).
34
35
 
35
36
  ```ruby
36
37
  RSpec.configure do |config|
@@ -109,18 +110,34 @@ This matcher will track ActiveRecord's built in load methods to prevent those
109
110
  N+1 situations. Using eager loading (e.g. `Track.all.includes(:album)`) will
110
111
  allow these expectations to pass as expected!
111
112
 
112
- **Note:** At the moment, this expectation will fail if you use a mechanism
113
- that loads records in batches, such as with `find_in_batches`. This will cause
114
- records to be "repeatedly loaded", but this is actually expected behavior in
115
- this case. If your tests load a relatively small number of records (which is
116
- probable), your tests won't fail. But it is possible.
113
+ ### Testing Batch Queries
114
+
115
+ If your code loads records in batches, it may be more difficult to create
116
+ expectations for repeated loading. After all, each batch will execute its own
117
+ queries, which may look like repeated loading.
118
+
119
+ If your test has a small enough number of records that only one batch is
120
+ loaded, your tests may work just fine. But otherwise, you may want to allow
121
+ your code to specify a batch size in order to guarantee only a single batch
122
+ is loaded.
123
+
124
+ ```ruby
125
+ tracks = Track.all
126
+
127
+ expect {
128
+ TrackSerializer.perform(tracks, batch_size: tracks.count)
129
+ }.not_to repeatedly_load('Track')
130
+ ```
117
131
 
118
132
  ## Counting Queries
119
133
 
120
- ### Types of Comparisons
134
+ Some services won't necessarily have N+1 issues with records loading, but might
135
+ still have problems with executing too many queries. In this case, the
136
+ `repeatedly_load` matcher might be insufficient.
121
137
 
122
- Several comparison types are available, along with some aliases to allow for
123
- easier to read tests.
138
+ In this case, consider creating an expectation on the total number of queries
139
+ executed by your code. Several comparison types are available, along with some
140
+ aliases to allow for easier to read tests.
124
141
 
125
142
  ```ruby
126
143
  expect {}.to execute.less_than(20).queries
@@ -143,15 +160,10 @@ expect {}.to execute.at_least(1).query # singular form also accepted
143
160
 
144
161
  ### Specific Query Types
145
162
 
146
- You can of course make assertions for the total number of queries executed, but
147
- sometimes it's more valuable to assert particular _types_ of queries, such as
148
- inserts or find statements. Matchers are supported for this as well!
149
-
150
- **Note:** Transaction (for example, `ROLLBACK`) queries are not counted in any of these
151
- categories, nor are queries that load the DB schema.
152
-
153
- **Note:** Destroy and delete queries are both condensed into the matcher for
154
- `destroy_queries`.
163
+ You can make assertions for the total number of queries executed, but sometimes
164
+ it's more valuable to assert that a particular _type_ of query was executed.
165
+ For example, a particular number of queries to destroy records. There are
166
+ matchers available for that purpose as well!
155
167
 
156
168
  ```ruby
157
169
  expect {}.to execute.exactly(20).queries
@@ -165,6 +177,74 @@ expect {}.to execute.exactly(20).schema_queries
165
177
  expect {}.to execute.exactly(20).transaction_queries
166
178
  ```
167
179
 
180
+ **Note:** Transaction (for example, `ROLLBACK`) queries are not counted in any of these
181
+ categories, nor are queries that load the DB schema.
182
+
183
+ **Note:** Destroy and delete queries are both condensed into the matcher for
184
+ the errorthe error `destroy_queries`.
185
+
186
+ ## Transaction Management
187
+
188
+ Sometimes, it makes sense to monitor whether database transactions were
189
+ successful or not. This is very similar to using `expect{}.to change(SomeModel,
190
+ :count)` in a spec, but nonetheless it can be useful to assert transactions
191
+ themselves. Some assertions are available for this purpose.
192
+
193
+ ```ruby
194
+ expect {}.to execute_a_transaction
195
+ expect {}.to rollback_a_transaction
196
+ expect {}.to roll_back_a_transaction
197
+ expect {}.to commit_a_transaction
198
+ ```
199
+
200
+ A complication to this scheme is that Rails tries not to make unnecessary database
201
+ calls, which means that attempting to save a model that has failing validations
202
+ won't actually attempt to save to the database.
203
+
204
+ ```ruby
205
+ expect {
206
+ MyClass.create!(required_field: nil)
207
+ }.to rollback_a_transaction
208
+ ```
209
+
210
+ This assertion will fail, as `create!` will never make it as far as the
211
+ database. That said, if you manually create a transaction, _and you select
212
+ data within that transaction_, you may assert a rollback.
213
+
214
+ ```ruby
215
+ expect {
216
+ MyClass.first # triggers the transaction
217
+ MyClass.create!(required_field: nil)
218
+ }.to rollback_a_transaction
219
+ ```
220
+
221
+ It you need to make transaction-related assertions of this sort, your best bet
222
+ may be to assert that a commit statement was _not_ issued.
223
+
224
+ ```ruby
225
+ expect do
226
+ MyClass.create!(required_field: nil)
227
+ rescue
228
+ # NOOP
229
+ end.not_to rollback_a_transaction
230
+ ```
231
+
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 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
+ ```
247
+
168
248
  ## Future Planned Functionality
169
249
 
170
250
  This gem still has lots of future functionality. See below.
@@ -176,10 +256,6 @@ expect {}.to execute.at_least(2).load_queries("Audited::Audit")
176
256
  expect {}.to execute.at_least(2).activerecord_queries
177
257
  expect {}.to execute.at_least(2).hand_rolled_queries
178
258
 
179
- expect {}.not_to rollback_transaction.exactly(5).times
180
- expect {}.not_to commit_transaction.once
181
- expect {}.to run_a_transaction
182
-
183
259
  expect {}.to create.exactly(5).of_type(User)
184
260
  expect {}.to insert.exactly(5).subscription_changes
185
261
  expect {}.to update.exactly(2).of_any_type
@@ -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
@@ -0,0 +1,99 @@
1
+ module RSpec::ActiveRecord::Expectations
2
+ module Matchers
3
+ class TransactionMatcher
4
+ attr_reader :collector, :quantifier, :comparison, :query_type
5
+
6
+ def initialize(transaction_type)
7
+ @collector = Collector.new
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
20
+ end
21
+
22
+ def supports_block_expectations?
23
+ true
24
+ end
25
+
26
+ def matches?(block)
27
+ raise NoComparisonError unless @match_method
28
+
29
+ block.call
30
+ result = @match_method.call
31
+ @collector.finalize
32
+
33
+ result
34
+ end
35
+
36
+ # QUANTIFIERS
37
+
38
+ def less_than(n)
39
+ @quantifier = n
40
+ @comparison = :less_than
41
+ @match_method = -> { actual_count < @quantifier }
42
+ self
43
+ end
44
+ alias_method :fewer_than, :less_than
45
+
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
59
+ end
60
+ alias_method :more_than, :greater_than
61
+
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
76
+
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
88
+
89
+ def times
90
+ self # NOOP
91
+ end
92
+ alias_method :time, :times
93
+
94
+ def actual_count
95
+ @collector.queries_of_type(@query_type)
96
+ end
97
+ end
98
+ end
99
+ 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
@@ -1,13 +1,18 @@
1
1
  module RSpec::ActiveRecord::Expectations
2
2
  class QueryInspector
3
3
  def self.valid_query_types
4
- [:queries, :schema_queries, :transaction_queries, :insert_queries,
5
- :load_queries, :destroy_queries, :exists_queries]
4
+ [:queries, :schema_queries, :insert_queries, :load_queries,
5
+ :destroy_queries, :exists_queries,
6
+ :transaction_queries, :commit_queries, :rollback_queries]
6
7
  end
7
8
 
8
9
  def categorize(query)
9
10
  if query[:name] == "SCHEMA"
10
11
  [:schema_queries]
12
+ elsif query[:sql] =~ /^commit/i
13
+ [:commit_queries]
14
+ elsif query[:sql] =~ /^rollback/i
15
+ [:rollback_queries]
11
16
  elsif query[:name] == "TRANSACTION"
12
17
  [:transaction_queries]
13
18
  elsif query[:name] =~ /Create$/
@@ -18,7 +23,7 @@ module RSpec::ActiveRecord::Expectations
18
23
  [:queries, :destroy_queries]
19
24
  elsif query[:name] =~ /Delete All$/
20
25
  [:queries, :destroy_queries]
21
- elsif query[:name] =~ /Exists\?$/
26
+ elsif query[:name] =~ /Exists\??$/
22
27
  [:queries, :exists_queries]
23
28
  else
24
29
  [:queries]
@@ -8,6 +8,22 @@ module RSpec
8
8
  def repeatedly_load(klass)
9
9
  Matchers::LoadMatcher.new(klass)
10
10
  end
11
+
12
+ def execute_a_transaction
13
+ Matchers::TransactionMatcher.new(:transaction_queries)
14
+ end
15
+
16
+ def rollback_a_transaction
17
+ Matchers::TransactionMatcher.new(:rollback_queries)
18
+ end
19
+
20
+ def roll_back_a_transaction
21
+ Matchers::TransactionMatcher.new(:rollback_queries)
22
+ end
23
+
24
+ def commit_a_transaction
25
+ Matchers::TransactionMatcher.new(:commit_queries)
26
+ end
11
27
  end
12
28
  end
13
29
  end
@@ -15,5 +31,7 @@ end
15
31
  require_relative 'expectations/errors'
16
32
  require_relative 'expectations/query_inspector'
17
33
  require_relative 'expectations/collector'
34
+ require_relative 'expectations/message_builder'
18
35
  require_relative 'expectations/matchers/query_count_matcher'
19
36
  require_relative 'expectations/matchers/load_matcher'
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 = '1.3.0'
3
+ spec.version = '2.3.0'
4
4
  spec.authors = ["Joseph Mastey"]
5
5
  spec.email = ["hello@joemastey.com"]
6
6
 
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.description = %q{Adds new matchers to rspec to help you test whether your code is executing an unreasonable number of queries.}
9
9
  spec.homepage = "https://github.com/jmmastey/rspec-activerecord-expectations"
10
10
  spec.license = "MIT"
11
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
11
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
12
12
 
13
13
  spec.metadata["homepage_uri"] = spec.homepage
14
14
  spec.metadata["source_code_uri"] = spec.homepage
@@ -23,7 +23,12 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ["lib"]
24
24
 
25
25
  spec.add_dependency "activerecord", ">= 5.0.0", "< 7.1.0"
26
- spec.add_dependency "sqlite3", "~> 1.0"
26
+
27
+ if RUBY_PLATFORM == 'java'
28
+ spec.add_development_dependency "activerecord-jdbcsqlite3-adapter", '>= 60'
29
+ else
30
+ spec.add_development_dependency "sqlite3", "~> 1.0"
31
+ end
27
32
 
28
33
  spec.add_development_dependency "pry", "~> 0.0"
29
34
  spec.add_development_dependency "appraisal", "~> 2"
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: 1.3.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-04 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
@@ -37,7 +37,7 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '1.0'
40
- type: :runtime
40
+ type: :development
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
@@ -104,6 +104,8 @@ files:
104
104
  - lib/rspec/activerecord/expectations/errors.rb
105
105
  - lib/rspec/activerecord/expectations/matchers/load_matcher.rb
106
106
  - lib/rspec/activerecord/expectations/matchers/query_count_matcher.rb
107
+ - lib/rspec/activerecord/expectations/matchers/transaction_matcher.rb
108
+ - lib/rspec/activerecord/expectations/message_builder.rb
107
109
  - lib/rspec/activerecord/expectations/query_inspector.rb
108
110
  - rspec-activerecord-expectations.gemspec
109
111
  homepage: https://github.com/jmmastey/rspec-activerecord-expectations
@@ -121,7 +123,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
121
123
  requirements:
122
124
  - - ">="
123
125
  - !ruby/object:Gem::Version
124
- version: 2.3.0
126
+ version: 2.5.0
125
127
  required_rubygems_version: !ruby/object:Gem::Requirement
126
128
  requirements:
127
129
  - - ">="