Skip to content

Commit

Permalink
refactor: exact matching
Browse files Browse the repository at this point in the history
  • Loading branch information
palkan committed Feb 17, 2023
1 parent ebfc865 commit 22c4f87
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 66 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

## master

- Added ability to specify the exact number of expected queries when using constant matchers. ([@akostadinov][], [@palkan][])

For RSpec, you can add the `.exactly` modifier:

```ruby
expect { get :index }.to perform_constant_number_of_queries.exactly(1)
```

For Minitest, you can provide the expected number of queries as the first argument:

```ruby
assert_perform_constant_number_of_queries(0, **options) do
get :index
end
```

- **Require Ruby 2.7+**.

## 0.6.2 (2021-10-26)
Expand Down Expand Up @@ -46,3 +62,4 @@ Could be specified via `NPLUSONE_BACKTRACE` env var.
[@palkan]: https://github.com/palkan
[@caalberts]: https://github.com/caalberts
[@andrewhampton]: https://github.com/andrewhampton
[@akostadinov]: https://github.com/akostadinov
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ expect { get :index }.to perform_constant_number_of_queries.matching(/INSERT/)

# You can also provide custom scale factors
expect { get :index }.to perform_constant_number_of_queries.with_scale_factors(10, 100)

# You can specify the exact number of expected queries
expect { get :index }.to perform_constant_number_of_queries.exactly(1)
```

#### Using scale factor in spec
Expand Down Expand Up @@ -214,6 +217,14 @@ assert_perform_constant_number_of_queries(
end
```

For the constant matcher, you can also specify the expected number of queries as the first argument:

```ruby
assert_perform_constant_number_of_queries(2, populate: populate) do
get :index
end
```

It's possible to specify a filter via `NPLUSONE_FILTER` env var, e.g.:

```ruby
Expand Down
31 changes: 6 additions & 25 deletions lib/n_plus_one_control/minitest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,8 @@
module NPlusOneControl
# Minitest assertions
module MinitestHelper
def assert_number_of_queries(
number,
populate: nil,
matching: nil,
warmup: nil
)

raise ArgumentError, "Block is required" unless block_given?

warming_up warmup

@executor = NPlusOneControl::Executor.new(
population: populate || population_method,
matching: (matching || NPlusOneControl.default_matching),
scale_factors: [1]
)

queries = @executor.call { yield }

counts = queries.map(&:last).map(&:size)

assert_equal number, counts.max, NPlusOneControl.failure_message(:number_of_queries, queries)
end

def assert_perform_constant_number_of_queries(
exact = nil,
populate: nil,
matching: nil,
scale_factors: nil,
Expand All @@ -50,7 +27,11 @@ def assert_perform_constant_number_of_queries(

counts = queries.map(&:last).map(&:size)

assert counts.max == counts.min, NPlusOneControl.failure_message(:constant_queries, queries)
if exact
assert counts.all? { _1 == exact }, NPlusOneControl.failure_message(:number_of_queries, queries)
else
assert counts.max == counts.min, NPlusOneControl.failure_message(:constant_queries, queries)
end
end

def assert_perform_linear_number_of_queries(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true

# rubocop:disable Metrics/BlockLength
::RSpec::Matchers.define :perform_constant_number_of_queries do
supports_block_expectations

Expand Down Expand Up @@ -43,7 +42,11 @@

counts = @queries.map(&:last).map(&:size)

counts.max == (@exactly || counts.min)
if @exactly
counts.all? { _1 == @exactly }
else
counts.max == counts.min
end
end

match_when_negated do |_actual|
Expand All @@ -52,4 +55,3 @@

failure_message { |_actual| NPlusOneControl.failure_message(@exactly ? :number_of_queries : :constant_queries, @queries) }
end
# rubocop:enable Metrics/BlockLength
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true

# rubocop:disable Metrics/BlockLength
::RSpec::Matchers.define :perform_linear_number_of_queries do |slope: 1|
supports_block_expectations

Expand Down Expand Up @@ -50,4 +49,3 @@

failure_message { |_actual| NPlusOneControl.failure_message(:linear_queries, @queries) }
end
# rubocop:enable Metrics/BlockLength
70 changes: 34 additions & 36 deletions tests/minitest_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,6 @@

require_relative "test_helper"

class TestMinitestSpecifiedQueries < Minitest::Test
def test_queries_match
populate = ->(n) { create_list(:post, n) }

assert_number_of_queries(1, populate: populate) do
Post.take
end
end

def test_queries_do_not_match
populate = ->(n) { create_list(:post, n) }

e = assert_raises Minitest::Assertion do
assert_number_of_queries(0, populate: populate) do
Post.take
end
end

assert_match "Expected to make the specified number of queries", e.message
assert_match "1 for N=1", e.message
assert_match(/posts \(SELECT\): 1$/, e.message)
end

def test_no_n_plus_one_error_with_matching
populate = ->(n) { create_list(:post, n) }

assert_number_of_queries(
1,
populate: populate,
matching: /posts/
) do
Post.find_each { |p| p.user.name }
end
end
end

class TestMinitestConstantQueries < Minitest::Test
def test_no_n_plus_one_error
populate = ->(n) { create_list(:post, n) }
Expand Down Expand Up @@ -96,6 +60,40 @@ def test_no_n_plus_one_error_with_warmup

assert warmed_up
end

def test_exact_number_queries_match
populate = ->(n) { create_list(:post, n) }

assert_perform_constant_number_of_queries(1, populate: populate) do
Post.take
end
end

def test_exact_number_queries_do_not_match
populate = ->(n) { create_list(:post, n) }

e = assert_raises Minitest::Assertion do
assert_perform_constant_number_of_queries(0, populate: populate, scale_factors: [1]) do
Post.take
end
end

assert_match "Expected to make the specified number of queries", e.message
assert_match "1 for N=1", e.message
assert_match(/posts \(SELECT\): 1$/, e.message)
end

def test_exact_number_error_with_matching
populate = ->(n) { create_list(:post, n) }

assert_perform_constant_number_of_queries(
1,
populate: populate,
matching: /posts/
) do
Post.find_each { |p| p.user.name }
end
end
end

class TestMinitestLinearQueries < Minitest::Test
Expand Down

0 comments on commit 22c4f87

Please sign in to comment.