expect_query 0.1.5 → 0.1.6
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/README.md +80 -15
- data/lib/expect_query/minitest.rb +30 -0
- data/lib/expect_query/rspec.rb +15 -21
- data/lib/expect_query/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 53cd97190ff208c5ddf0f7ea4b2defad56c2651766e25b2faa70e2d495c30774
|
|
4
|
+
data.tar.gz: da83d3a86398c231e636a0b7ba7938ed16f690a43a9085d7fc5fd6857ed6d34f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 60cb5e8d7672d3124c2a21e4f22eebbe9b29aa3e0d214cb921b568af3f21b5101716b3af6773056734a44c97331b7a60163b579a8f0dfbc75d92e43167753b31
|
|
7
|
+
data.tar.gz: aeb1ba386cc83d8bb8ace523657ec557fef0a598c4f579631262fb3eb9a347a927cdeebc09d22c3677d49bd900d69c5845901697ac5a655009fe4e2f601e081c
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ExpectQuery
|
|
2
2
|
|
|
3
|
-
`expect_query` provides Rails 8.0+ compatible assertions and matchers for counting SQL queries and Cache operations. It supports both RSpec and Minitest.
|
|
3
|
+
`expect_query` provides Rails 8.0+ compatible assertions and matchers for counting SQL queries and Cache operations. It supports both RSpec and Minitest, offering powerful tools to prevent N+1 queries and ensure efficient caching.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -22,55 +22,79 @@ require "expect_query/rspec"
|
|
|
22
22
|
|
|
23
23
|
#### SQL Assertions
|
|
24
24
|
|
|
25
|
+
Verify the number of SQL queries executed.
|
|
26
|
+
|
|
25
27
|
```ruby
|
|
28
|
+
# Exact count
|
|
26
29
|
expect {
|
|
27
30
|
User.first
|
|
28
31
|
}.to make_sql_queries(1)
|
|
29
32
|
|
|
33
|
+
# At most (useful for reducing N+1 without being brittle)
|
|
34
|
+
expect {
|
|
35
|
+
User.all.each(&:profile)
|
|
36
|
+
}.to make_sql_queries(at_most: 3)
|
|
37
|
+
|
|
38
|
+
# Filter by regex matching SQL
|
|
30
39
|
expect {
|
|
31
40
|
User.create(name: "Foo")
|
|
32
41
|
}.to make_sql_queries(1, matching: /INSERT/)
|
|
33
|
-
|
|
34
|
-
expect {
|
|
35
|
-
# ...
|
|
36
|
-
}.to make_sql_queries(at_most: 3)
|
|
37
42
|
```
|
|
38
43
|
|
|
39
44
|
#### Cache Assertions
|
|
40
45
|
|
|
46
|
+
Verify cache operations (read, write, fetch, etc.).
|
|
47
|
+
|
|
41
48
|
```ruby
|
|
49
|
+
# Count specific operations
|
|
42
50
|
expect {
|
|
43
51
|
Rails.cache.read("foo")
|
|
44
52
|
Rails.cache.write("bar", 1)
|
|
45
53
|
}.to perform_cache_operations(read: 1, write: 1)
|
|
46
54
|
|
|
55
|
+
# Count total operations
|
|
47
56
|
expect {
|
|
48
57
|
Rails.cache.fetch("foo") { 1 }
|
|
49
|
-
}.to perform_cache_operations(total: 2) # read + write
|
|
58
|
+
}.to perform_cache_operations(total: 2) # 1 read (miss) + 1 write
|
|
59
|
+
|
|
60
|
+
# Filter by key matching regex
|
|
61
|
+
expect {
|
|
62
|
+
Rails.cache.read("user:1")
|
|
63
|
+
Rails.cache.read("post:1")
|
|
64
|
+
}.to perform_cache_operations(read: 2, matching: /user|post/)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Chaining Matchers
|
|
68
|
+
|
|
69
|
+
You can verify both SQL and Cache operations in a single block using RSpec's compound matchers.
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
expect {
|
|
73
|
+
User.create(name: "Alice")
|
|
74
|
+
Rails.cache.write("user:alice", "data")
|
|
75
|
+
}.to make_sql_queries(1).and perform_cache_operations(write: 1)
|
|
50
76
|
```
|
|
51
77
|
|
|
52
78
|
### Multiple Cache Stores
|
|
53
79
|
|
|
54
|
-
By default, assertions use `Rails.cache
|
|
80
|
+
By default, assertions use `Rails.cache`. You can specify a different store or multiple stores using `store` or `stores` options.
|
|
55
81
|
|
|
56
82
|
```ruby
|
|
57
|
-
#
|
|
83
|
+
# Check specific store
|
|
58
84
|
expect {
|
|
59
85
|
my_cache.write("foo", 1)
|
|
60
86
|
}.to perform_cache_operations(write: 1, store: my_cache)
|
|
61
87
|
|
|
88
|
+
# Check multiple stores
|
|
62
89
|
expect {
|
|
63
90
|
cache1.write("a", 1)
|
|
64
91
|
cache2.write("b", 1)
|
|
65
92
|
}.to perform_cache_operations(total: 2, stores: [cache1, cache2])
|
|
66
93
|
```
|
|
67
94
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
my_cache.write("foo", 1)
|
|
72
|
-
end
|
|
73
|
-
```
|
|
95
|
+
### Minitest
|
|
96
|
+
|
|
97
|
+
Add this to your `test_helper.rb`:
|
|
74
98
|
|
|
75
99
|
```ruby
|
|
76
100
|
require "expect_query/minitest"
|
|
@@ -79,18 +103,60 @@ require "expect_query/minitest"
|
|
|
79
103
|
#### SQL Assertions
|
|
80
104
|
|
|
81
105
|
```ruby
|
|
106
|
+
# Exact count
|
|
82
107
|
assert_sql_queries(1) do
|
|
83
108
|
User.first
|
|
84
109
|
end
|
|
110
|
+
|
|
111
|
+
# At most
|
|
112
|
+
assert_sql_queries(at_most: 3) do
|
|
113
|
+
User.all.map(&:name)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Regex matching
|
|
117
|
+
assert_sql_queries(1, matching: /INSERT/) do
|
|
118
|
+
User.create(name: "Foo")
|
|
119
|
+
end
|
|
85
120
|
```
|
|
86
121
|
|
|
87
122
|
#### Cache Assertions
|
|
88
123
|
|
|
89
124
|
```ruby
|
|
125
|
+
# Specific operations
|
|
90
126
|
assert_cache_operations(read: 1, write: 1) do
|
|
91
127
|
Rails.cache.read("foo")
|
|
92
128
|
Rails.cache.write("bar", 1)
|
|
93
129
|
end
|
|
130
|
+
|
|
131
|
+
# Total operations
|
|
132
|
+
assert_cache_operations(total: 2) do
|
|
133
|
+
Rails.cache.fetch("foo") { 1 }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Specific store
|
|
137
|
+
assert_cache_operations(write: 1, store: my_cache) do
|
|
138
|
+
my_cache.write("foo", 1)
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Combined Assertions (IO Operations)
|
|
143
|
+
|
|
144
|
+
Use `assert_io_operations` to verify both SQL and Cache operations in a single block.
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
assert_io_operations(sql: { count: 1 }, cache: { write: 1 }) do
|
|
148
|
+
User.create(name: "Alice")
|
|
149
|
+
Rails.cache.write("user:alice", "data")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# With options
|
|
153
|
+
assert_io_operations(
|
|
154
|
+
sql: { count: 1, matching: /INSERT/ },
|
|
155
|
+
cache: { read: 1, matching: /user:/ }
|
|
156
|
+
) do
|
|
157
|
+
User.create(name: "Bob")
|
|
158
|
+
Rails.cache.read("user:bob")
|
|
159
|
+
end
|
|
94
160
|
```
|
|
95
161
|
|
|
96
162
|
## Failure Messages
|
|
@@ -125,4 +191,3 @@ bundle exec rake
|
|
|
125
191
|
```
|
|
126
192
|
|
|
127
193
|
This runs both RSpec and Minitest suites.
|
|
128
|
-
|
|
@@ -66,6 +66,36 @@ module ExpectQuery
|
|
|
66
66
|
assert_equal expected, actual, "Expected #{expected} #{op} cache operations, but got #{actual}#{debug_info}"
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
|
+
|
|
70
|
+
def assert_io_operations(sql: nil, cache: nil, &block)
|
|
71
|
+
if sql
|
|
72
|
+
sql_args = sql.dup
|
|
73
|
+
count = sql_args.delete(:count)
|
|
74
|
+
matching = sql_args.delete(:matching)
|
|
75
|
+
at_most = sql_args.delete(:at_most)
|
|
76
|
+
|
|
77
|
+
# If unknown keys remain in sql_args, we could warn, but for now we verify strictness if possible?
|
|
78
|
+
# assert_sql_queries doesn't take **options, so we can't pass them.
|
|
79
|
+
|
|
80
|
+
inner_block = if cache
|
|
81
|
+
proc { assert_io_operations(cache: cache, &block) }
|
|
82
|
+
else
|
|
83
|
+
block
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
assert_sql_queries(count, matching: matching, at_most: at_most, &inner_block)
|
|
87
|
+
elsif cache
|
|
88
|
+
cache_args = cache.dup
|
|
89
|
+
count = cache_args.delete(:count)
|
|
90
|
+
matching = cache_args.delete(:matching)
|
|
91
|
+
store = cache_args.delete(:store)
|
|
92
|
+
stores = cache_args.delete(:stores)
|
|
93
|
+
|
|
94
|
+
assert_cache_operations(count, matching: matching, store: store, stores: stores, **cache_args, &block)
|
|
95
|
+
else
|
|
96
|
+
block.call
|
|
97
|
+
end
|
|
98
|
+
end
|
|
69
99
|
end
|
|
70
100
|
end
|
|
71
101
|
|
data/lib/expect_query/rspec.rb
CHANGED
|
@@ -25,22 +25,22 @@ module ExpectQuery
|
|
|
25
25
|
true
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
@actual = @counter.count
|
|
35
|
-
|
|
36
|
-
if @at_most
|
|
37
|
-
@actual <= @at_most
|
|
38
|
-
elsif @expected
|
|
39
|
-
@actual == @expected
|
|
40
|
-
else
|
|
41
|
-
@actual > 0 # generic "makes queries"
|
|
42
|
-
end
|
|
28
|
+
def matches?(block)
|
|
29
|
+
@counter = ExpectQuery::SqlCounter.new(matching: @matching)
|
|
30
|
+
ActiveSupport::Notifications.subscribed(@counter, "sql.active_record") do
|
|
31
|
+
block.call
|
|
43
32
|
end
|
|
33
|
+
|
|
34
|
+
@actual = @counter.count
|
|
35
|
+
|
|
36
|
+
if @at_most
|
|
37
|
+
@actual <= @at_most
|
|
38
|
+
elsif @expected
|
|
39
|
+
@actual == @expected
|
|
40
|
+
else
|
|
41
|
+
@actual > 0 # generic "makes queries"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
44
|
|
|
45
45
|
def failure_message
|
|
46
46
|
msg = "expected to make "
|
|
@@ -86,12 +86,6 @@ module ExpectQuery
|
|
|
86
86
|
store_ids << store.object_id
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
# If no stores are defined (e.g. no Rails.cache and no store passed), we can't strict filter by ID.
|
|
90
|
-
# But we probably should pass empty array if we intended to track but found nothing?
|
|
91
|
-
# Or if store_ids is empty, maybe we track ALL?
|
|
92
|
-
# User said: "By default use Rails.cache". If Rails.cache is missing, maybe we should warn or track nothing?
|
|
93
|
-
# Let's pass nil if we want to track ALL, but if we defaulted to Rails.cache and it's there, we pass it.
|
|
94
|
-
# If user explicitly passed empty list?
|
|
95
89
|
pass_ids = store_ids.empty? ? nil : store_ids
|
|
96
90
|
|
|
97
91
|
@counter = ExpectQuery::CacheCounter.new(matching: @matching, store_ids: pass_ids)
|
data/lib/expect_query/version.rb
CHANGED