Mocks on Rails

Posted by James Mead Thu, 31 Aug 2006 04:25:00 GMT

It’s great to see that Gluttonous has been playing with Mocha. Like many people, he’s found the ability to mock or stub class methods particularly useful – and this is one of the key differentiators between Mocha and other Ruby mocking libraries.

He’s been trying to improve the test coverage for Rails and submitted this patch where there is an interesting discussion1 about why he would prefer to use Mocha. Interestingly, there has also been some recent discussion on the RSpec mailing list about adding Mocha-like functionality.

On a different note, in his article Mocks for Speed, Gluttonous draws attention to one of the advantages I have previously mentioned of using mocks extensively to write unit tests that test a class in isolation – namely a fast build.

1 We have now released Mocha under the MIT license so it can be used for testing within Rails.

Tags , , , , , , , , , , ,  | 4 comments

AutoMocha example

Posted by James Mead Sun, 16 Jul 2006 19:01:00 GMT

Again – I’m not really very pleased with this example, but hopefully it makes some sense. It’s important to realise that the test is not running in a normal Rails environment with the standard auto-require. In fact the first time the Comment class is encountered AutoMocha uses const_missing to supply a Mocha::Mock in its place. From that point on – any further references get the same mock object.

  class Article

    attr_reader :id

    def accepted_comments
      Comment.find_all_by_article_id(self.id).select { |comment| comment.accepted? }
    end

  end

  require 'rubygems'
  require 'auto_mocha'
  require 'test/unit'

  class OrderTest < Test::Unit::TestCase

    include Mocha

    # illustrates stubbing of previously undefined class Comment
    def test_should_return_accepted_comments_for_this_article
      unaccepted_comment = Mock.new(:accepted? => false)
      accepted_comment = Mock.new(:accepted? => true)
      comments = [unaccepted_comment, accepted_comment]
      Comment.stubs(:find_all_by_article_id).returns(comments)
      article = Article.new
      assert_equal [accepted_comment], article.accepted_comments
    end

  end

Tags , , , , , , , , , , ,  | no comments

Stubba example

Posted by James Mead Sun, 16 Jul 2006 19:00:00 GMT

I don’t really like this example, but it’s the best I’ve got at the moment. The any_instance bit is particularly contrived, but hopefully you get the idea.

  class Order

    attr_accessor :shipped_on

    def total_cost
      line_items.inject(0) { |total, line_item| total + line_item.price } + shipping_cost
    end

    def total_weight
      line_items.inject(0) { |total, line_item| total + line_item.weight }
    end

    def shipping_cost
      total_weight * 5 + 10
    end

    class << self

      def find_all
        # Database.connection.execute('select * from orders...
      end

      def number_shipped_since(date)
        find_all.select { |order| order.shipped_on > date }.size
      end

      def unshipped_value
        find_all.inject(0) { |total, order| order.shipped_on ? total : total + order.total_cost }
      end

    end

  end

  require 'rubygems'
  require 'stubba'
  require 'test/unit'

  class OrderTest < Test::Unit::TestCase

    # illustrates stubbing instance method
    def test_should_calculate_shipping_cost_based_on_total_weight
      order = Order.new
      order.stubs(:total_weight).returns(10)
      assert_equal 60, order.shipping_cost
    end

    # illustrates stubbing class method
    def test_should_count_number_of_orders_shipped_after_specified_date
      now = Time.now; week_in_secs = 7 * 24 * 60 * 60
      order_1 = Order.new; order_1.shipped_on = now - 1 * week_in_secs
      order_2 = Order.new; order_2.shipped_on = now - 3 * week_in_secs
      Order.stubs(:find_all).returns([order_1, order_2])
      assert_equal 1, Order.number_shipped_since(now - 2 * week_in_secs)
    end

    # illustrates stubbing instance method for all instances of a class
    def test_should_calculate_value_of_unshipped_orders
      Order.stubs(:find_all).returns([Order.new, Order.new, Order.new])
      Order.any_instance.stubs(:shipped_on).returns(nil)
      Order.any_instance.stubs(:total_cost).returns(10)
      assert_equal 30, Order.unshipped_value
    end

  end

Tags , , , , , , , , ,  | no comments

Mocha example

Posted by James Mead Sun, 16 Jul 2006 18:59:00 GMT

I thought I’d put the examples from the RDoc README up here if only for the syntax highlighting.

  class Enterprise

    def initialize(dilithium)
      @dilithium = dilithium
    end

    def go(warp_factor)
      warp_factor.times { @dilithium.nuke(:anti_matter) }
    end

  end

  require 'rubygems'
  require 'mocha'
  require 'test/unit'

  class EnterpriseTest < Test::Unit::TestCase

    include Mocha

    def test_should_boldly_go
      dilithium = Mock.new
      dilithium.expects(:nuke).with(:anti_matter).at_least_once
      enterprise = Enterprise.new(dilithium)
      enterprise.go(2)
      dilithium.verify
    end

  end

Tags , , , , , , , , ,  | no comments