Blog

Showing posts with label testing. Show all posts
Showing posts with label testing. Show all posts

Friday, March 9, 2012

Testing Terminology: Spies, Stubs and Mocks

When referring to the definitions of Spies, Stubs and Mocks I like to return to XUnit Test Patterns:

  • Test Spy: "We use a Test Double to capture the indirect output calls made to another component by the SUT for later verification by the test."
  • Test Stub: "We replace a real object with a test-specific object that feeds the desired indirect inputs into the SUT."
  • Mock Object: "We replace an object the SUT depends on with a test-specific object that verifies it is being used correctly by the SUT."

You may have noticed that a Spy and Mock seem to have similar definitions, and do serve similar purposes, however Mocks specify expectations up-front and don't require assertions in contrast to Spies, that verify after the fact. Here's an example that compares Rspec Mocks with Bourne's Spies:

SUT is an abbreviation for System Under Test.


The above patterns are collectively know as Test Doubles: "We replace a component on which the SUT depends with a test-specific equivalent." Rspec's built-in mocking library includes a double method/alias, see An Example using RSpec double, mock, and stub.

Double-Ruby (RR) is another example of a test double framework implemented in Ruby.

Sunday, February 6, 2011

4 Things I Assert in Rails Controller Tests

  1. ActiveRecord Calls (using mocking)
  2. Assigns
  3. Flash
  4. Responses

And if the response content type is not going to be HTML I will test that as well.

Thursday, December 30, 2010

Specifying Behaviour vs Specifying Implementation

At the JRubyConf in Columbus, Ohio last October, Jim Weirich presented "Testing – Why Don't We Do It Like This?". At the end of the presentation he suggested asking two questions to ensure our tests are specifying behaviour rather than implementation:

  1. If I wanted to use this software in my project, what behaviours are important to me?
  2. Could I write this software from scratch using only the tests/specs as guidance?

These are definitely useful questions to ask, rather than blindly writing specs to simply get 100% test coverage. Check the entire presentation below:

Testing – Why Don't We Do It Like This? – Jim Weirich from Engine Yard on Vimeo.

Sunday, December 6, 2009

Use Stubs and Expectations to Simplify Testing with Named Scopes

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.

Please note this blog is no longer maintained. Please visit CivilCode Inc - Custom Software Development.