Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This matcher allows you to spec the number of database calls made by a method.
- Loading branch information
Showing
3 changed files
with
143 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
lib/shoulda/matchers/active_record/query_the_database_matcher.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
module Shoulda # :nodoc: | ||
module Matchers | ||
module ActiveRecord # :nodoc: | ||
|
||
# Ensures that the number of database queries is known. Rails 3.1 or greater is required. | ||
# | ||
# Options: | ||
# * <tt>when_calling</tt> - Required, the name of the method to examine. | ||
# * <tt>with</tt> - Used in conjunction with <tt>when_calling</tt> to pass parameters to the method to examine. | ||
# * <tt>or_less</tt> - Pass if the database is queried no more than the number of times specified, as opposed to exactly that number of times. | ||
# | ||
# Examples: | ||
# it { should query_the_database(4.times).when_calling(:complicated_counting_method) | ||
# it { should query_the_database(4.times).or_less.when_calling(:generate_big_report) | ||
# it { should_not query_the_database.when_calling(:cached_count) | ||
# | ||
def query_the_database(times = nil) | ||
QueryTheDatabaseMatcher.new(times) | ||
end | ||
|
||
class QueryTheDatabaseMatcher # :nodoc: | ||
|
||
def initialize(times) | ||
if times.respond_to?(:count) | ||
@expected_query_count = times.count | ||
else | ||
@expected_query_count = times | ||
end | ||
end | ||
|
||
def when_calling(method_name) | ||
@method_name = method_name | ||
self | ||
end | ||
|
||
def with(*method_arguments) | ||
@method_arguments = method_arguments | ||
self | ||
end | ||
|
||
def or_less | ||
@expected_count_is_maximum = true | ||
self | ||
end | ||
|
||
def matches?(subject) | ||
@queries = [] | ||
|
||
subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*args| | ||
@queries << args[4] unless filter_query(args[4][:name]) | ||
end | ||
|
||
subject.send(@method_name, *@method_arguments) | ||
|
||
ActiveSupport::Notifications.unsubscribe(subscriber) | ||
|
||
if @expected_count_is_maximum | ||
@queries.length <= @expected_query_count | ||
elsif @expected_query_count.present? | ||
@queries.length == @expected_query_count | ||
else | ||
@queries.length > 0 | ||
end | ||
end | ||
|
||
def failure_message | ||
if @expected_query_count | ||
"Expected ##{@method_name} to cause #{@expected_query_count} database queries but it actually caused #{@queries.length} queries:" + friendly_queries | ||
else | ||
"Expected ##{@method_name} to not query the database but it actually caused #{@queries.length} queries:" + friendly_queries | ||
end | ||
end | ||
|
||
alias_method :negative_failure_message, :failure_message | ||
|
||
private | ||
|
||
def friendly_queries | ||
@queries.collect do |query| | ||
"\n (#{query[:name]}) #{query[:sql]}" | ||
end.join | ||
end | ||
|
||
def filter_query(query_name) | ||
raise "Rails 3.1 or greater is required" unless rails_3_1? | ||
query_name == 'SCHEMA' | ||
end | ||
|
||
def rails_3_1? | ||
::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR >= 1 | ||
end | ||
|
||
end | ||
end | ||
end | ||
end |
46 changes: 46 additions & 0 deletions
46
spec/shoulda/active_record/query_the_database_matcher_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
require 'spec_helper' | ||
|
||
describe Shoulda::Matchers::ActiveRecord::QueryTheDatabaseMatcher do | ||
|
||
if ::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR >= 1 | ||
|
||
before do | ||
@parent = define_model :litter do | ||
has_many :kittens | ||
end | ||
@child = define_model :kitten, :litter_id => :integer do | ||
belongs_to :litter | ||
end | ||
end | ||
|
||
it "should accept the correct number of queries when there is a single query" do | ||
@parent.should query_the_database(1.times).when_calling(:count) | ||
end | ||
|
||
it "should accept the correct number of queries when there are two queries" do | ||
nonsense = lambda do | ||
@parent.create.kittens.create | ||
end | ||
nonsense.should query_the_database(2.times).when_calling(:call) | ||
end | ||
|
||
it "should reject the wrong number of queries" do | ||
@parent.should_not query_the_database(10.times).when_calling(:count) | ||
end | ||
|
||
it "should accept fewer than the required count" do | ||
@parent.should query_the_database(5.times).or_less.when_calling(:count) | ||
end | ||
|
||
it "should pass arguments to the method to examine" do | ||
model = Class.new do | ||
def self.count(arguments) | ||
arguments.should == "arguments" | ||
end | ||
end | ||
model.should_not query_the_database.when_calling(:count).with("arguments") | ||
end | ||
|
||
end | ||
|
||
end |