Summary: Use stubs to return the original class for a named scope, and use expectations to specify scope requirements to simplify testing
Testing with chained named scopes that have complex attributes such as conditions can be tricky. I try to combine named scopes with clear names to communicate intent, rather than have long chains floating around in code. This can be done simply by creating a plain old method, with scoped procedures or this hack. This is a good way to keep named scopes DRY when using a specific combination across your application code. Of course you should test your named scopes.
However, consider the situation when adding a simple extension to your named scope that doesn't warrant a new named scope or combination. As an example I have the following code in a catalog application:
class Catalog
...
# Returns a set of ordered page numbers based on the product page numbers
def page_numbers
Product.printable.find(:all, :select => "DISTINCT products.page_number").
collect {|product| product.page_number}
end
...
end
The printable named scope is quite complex and has been tested. I wanted to avoid duplicating test data (although factories would help here). For this method I was only concerned about a collection of products and it's page numbers, not that fact it was scoped. But it is a requirement that the products are scoped to only those that are printable.
Stubbing and setting expectations help us simplify the test and express the requirement. By stubbing the named scope and returning the Product class we can focus on creating test data for which we are concerned -- products with page numbers. The expectation "find printable products", ensures that we have scoped products as required.
class CatalogTest < ActiveSupport::TestCase
context "with page numbers" do
setup do
Product.stubs(:printable).returns(Product)
end
context "generally" do
setup do
Catalog.page_numbers
end
before_should "find printable products" do
Product.expects(:printable).returns(Product)
end
end
context "when no products" do
should "return an empty array" do
assert_equal [], Catalog.page_numbers
end
end
context "when multiple products" do
setup do
Factory(:catalog_with_page_numbers)
end
should "return an array of page numbers" do
assert_equal [0, 1, 2, 3], Catalog.page_numbers
end
end
end
end
Although setting this expectation increases coupling to the implementation, we are justified in that it expresses the requirement in the test example.