Mocha build status Gem Version

Description

  • A Ruby library for mocking and stubbing.
  • A unified, simple and readable syntax for both full & partial mocking.
  • Built-in support for MiniTest and Test::Unit.
  • Supported by many other test frameworks.

Installation

Gem

Install the latest version of the gem with the following command...

$ gem install mocha

Note: If you are intending to use Mocha with Test::Unit or MiniTest, you should only load Mocha after loading the relevant test library...

require "test/unit"
require "mocha/setup"

Bundler

If you're using Bundler, ensure the correct load order by not auto-requiring Mocha in the Gemfile and then load it later once you know the test library has been loaded...

# Gemfile
gem "mocha", :require => false

# Elsewhere after Bundler has loaded gems
require "test/unit"
require "mocha/setup"

Rails

If you're loading Mocha using Bundler within a Rails application, you should ensure Mocha is not auto-required (as above) and load Mocha manually e.g. at the bottom of your test_helper.rb.

# Gemfile in Rails app
gem "mocha", :require => false

# At bottom of test_helper.rb
require "mocha/setup"

Note: Using the latest version of Mocha (0.13.1) with the latest versions of Rails (e.g. 3.2.11, 3.1.10, or 3.0.19), you will see the following Mocha deprecation warning:

*** Mocha deprecation warning: Change `require 'mocha'` to `require 'mocha/setup'`.

This will happen until new versions of Rails are released incorporating the following pull requests:

The deprecation warning will not cause any problems, but if you don't like seeing then you could do one of the following:

Option 1 - Disable Mocha deprecation warnings with a Rails initializer
# config/mocha.rb
if Rails.env.test? || Rails.env.development?
  require "mocha/version"
  require "mocha/deprecation"
  if Mocha::VERSION == "0.13.2" && Rails::VERSION::STRING == "3.2.11"
    Mocha::Deprecation.mode = :disabled
  end
end

Note 1: I have intentionally made this brittle against patch releases of Mocha and Rails, because this way you are more likely to notice when you no longer need the initializer. Note 2: This will not work with recent versions of the Test::Unit gem (see below).

Option 2 - Use "edge" Rails or one of the relevant "stable" branches of Rails
# Gemfile
gem "rails", git: "git://github.com/rails/rails.git", branch: "3-2-stable"
group :test do
  gem "mocha", :require => false
end

# test/test_helper.rb
require "mocha/setup"
Option 3 - Downgrade to Mocha 0.12.8
# Gemfile in Rails app
gem "mocha", "~> 0.12.8", :require => false

# At bottom of test_helper.rb
require "mocha"

Note: This isn't as bad as it sounds, because there aren't many changes in Mocha 0.13.x that are not in 0.12.x.

Rails with Test::Unit

Unfortunately due to Rails relying on Mocha & Test::Unit internals, there is a problem with using recent released versions of Rails with the latest version of Mocha and recent versions of the Test::Unit gem.

In this case you will have to use either Option 2 or Option 3 (see above). Option 1 will not work.

Rails Plugin

Install the Rails plugin...

$ rails plugin install git://github.com/freerange/mocha.git

Note: As of version 0.9.8, the Mocha plugin is not automatically loaded at plugin load time. Instead it must be manually loaded e.g. at the bottom of your test_helper.rb.

Know Issues

  • Versions 0.10.2, 0.10.3 & 0.11.0 of the Mocha gem were broken.
  • Versions 0.9.6 & 0.9.7 of the Mocha Rails plugin were broken.
  • Please do not use these versions.

Usage

Quick Start

require 'test/unit'
require 'mocha/setup'

class MiscExampleTest < Test::Unit::TestCase
  def test_mocking_a_class_method
    product = Product.new
    Product.expects(:find).with(1).returns(product)
    assert_equal product, Product.find(1)
  end

  def test_mocking_an_instance_method_on_a_real_object
    product = Product.new
    product.expects(:save).returns(true)
    assert product.save
  end

  def test_stubbing_instance_methods_on_real_objects
    prices = [stub(:pence => 1000), stub(:pence => 2000)]
    product = Product.new
    product.stubs(:prices).returns(prices)
    assert_equal [1000, 2000], product.prices.collect {|p| p.pence}
  end

  def test_stubbing_an_instance_method_on_all_instances_of_a_class
    Product.any_instance.stubs(:name).returns('stubbed_name')
    product = Product.new
    assert_equal 'stubbed_name', product.name
  end

  def test_traditional_mocking
    object = mock('object')
    object.expects(:expected_method).with(:p1, :p2).returns(:result)
    assert_equal :result, object.expected_method(:p1, :p2)
  end

  def test_shortcuts
    object = stub(:method1 => :result1, :method2 => :result2)
    assert_equal :result1, object.method1
    assert_equal :result2, object.method2
  end
end

Mock Objects

class Enterprise
  def initialize(dilithium)
    @dilithium = dilithium
  end

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

require 'test/unit'
require 'mocha/setup'

class EnterpriseTest < Test::Unit::TestCase
  def test_should_boldly_go
    dilithium = mock()
    dilithium.expects(:nuke).with(:anti_matter).at_least_once  # auto-verified at end of test
    enterprise = Enterprise.new(dilithium)
    enterprise.go(2)
  end
end

Partial Mocking

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 }.length
    end

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

require 'test/unit'
require 'mocha/setup'

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

Contributing

  • Fork the repository.
  • Make your changes in a branch (including tests).
  • Ensure all of the tests run against all supported versions of Test::Unit, MiniTest & Ruby by running ./build-matrix.rb (note that currently this makes a lot of assumptions about your local environment e.g. rbenv installed with specific Ruby versions).
  • Send us a pull request from your fork/branch.

Contributors

See this list of contributors.

Translations

History

Mocha was initially harvested from projects at Reevoo. It's syntax is heavily based on that of jMock.

License

© Copyright Revieworld Ltd. 2006

You may use, copy and redistribute this library under the same terms as Ruby itself or under the MIT license.