Ultimate rspec matcher to test named_scope or scoped
        11 Jul 2010
    
    After having a good practice on using Ultimate rspec matcher to test validation I think it's time to implement one for testing named scopes - custom finders. Testing these finders is daily task. Here is how it can be done with minimum amount of code and maximum readability.
Discovery (Animal Planet)
What do we expect from the custom finder?
We expect that it should find assets A, B, C and should not find assets D, E, F.
And sometimes the order is important: it should find A, B C with exact order.
With respect to let rspec feature let's take an example: Product has and belongs to many categories. We need to have a scope to filter products within the specified category:
describe "#by_category_id" do
      let(:given_category) do 
        Factory.create(:given_category)
      end
 
      let(:product_in_given_category) do
        Factory.create(
          :product,
          :categories => [category]
        )
      end
      let(:product_not_in_given_category) do
        Factory.create(
          :product,
          :categories => [Factory.create(:category)]
        )
      end
      # This might be tricky to redefine subject as the finder result
      # but in this way we can delegate the matcher to subject and 
      # avoid writing test descriptions.
      subject { Product.by_category_id(given_category.id) }
      it { should discover(product_in_given_category) }
      it { should_not discover(product_not_in_given_category) }
    end
Note: Be aware of the lazy loading of your finder. let is initialized lazy too. You should make sure it is called before the actual query to the database. If you don't want to care about lazy loading their is let! method that could be easily copy-pasted from Rspec 2.0. Unlike let it doesn't have lazy initialization:
def let!(name, &block)
  let(name, &block)
  before { __send__(name) } 
end
Testing sort order
If the ordering is done in non-trivial way let's discover.with_exact_order.describe "#most_commented named scope" do
  let(:uncommented_post) { Factory.create(:post)}
  let!(:less_commented_post) { Factory.create(:post, :comments => [Factory.build(:comment)])}
  let!(:more_commented_post) { 
    Factory.create(:post, :comments => [Factory.build(:comment), Factory.build(:comment)])}
  }
  subject { described_class.most_commented }
 it {should discover(more_commented_post, less_commented_post).with_exact_order }
 it {should_not discover(uncommented_post) }
end 
        
         
                