Posted by James Mead
Fri, 01 Sep 2006 14:07:00 GMT
I really must get round to writing some better documentation for Mocha, but in the meantime here’s a quickstart guide1.
Mocha adds a couple of new methods to all objects and classes – expects which sets up auto-verified expectations and stubs which stubs the method allowing any number of calls.
Both the expects and stubs methods actually return an expectation object. Relevant methods on an expectation are: at_least, at_least_once, never, raises, returns, times, with, yields which are hopefully fairly self-explanatory. If not there are some clues here
Mocking a class method
product = Product.new
Product.expects(:find).with(1).returns(product)
assert_equal product, Product.find(1)
Mocking an instance method on a real object
product = Product.new
product.expects(:save).returns(true)
assert product.save
Stubbing instance methods on real object
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}
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
Traditional mocking
object = mock()
object.expects(:expected_method).with(:p1, :p2).returns(:result)
assert_equal :result, object.expected_method(:p1, :p2)
Shortcuts
object = stub(:method1 => :result1, :method2 => :result2)
assert_equal :result1, object.method1
assert_equal :result2, object.method2
1 I wrote these examples without checking them, so there may be some typos.
Tags class, instance, interception, mock, quickstart, rails, ruby, stub, testing, tutorial | 3 comments
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 class, fixture, instance, method, mock, mocking, rails, ruby, stub, stubbing, test, testing | 4 comments
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
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 class, dependency, expectation, method, mock, mocking, rails, ruby, stub, stubbing, test, testing | no comments
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
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
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
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
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 class, expectation, method, mock, mocking, ruby, stub, stubbing, test, testing | no comments
Posted by James Mead
Sun, 16 Jul 2006 17:51:00 GMT
While developing Mocha, I have to admit I’ve been somewhat remiss in keeping up-to-date with the latest developments on projects like RSpec and FlexMock. It looks like both projects have gone further than Mocha with implementing the JMock concepts. FlexMock also now supports simple class interception (see FlexMock::TestCase#intercept), but it admits to being “very simple-minded”, has “a number of restrictions” and “a proxy class constant will be left behind”.
I don’t think Stubba suffers from these limitations, but I’ll save a more detailed explanation for another post. It doesn’t look like either project has anything like AutoMocha.
Tags class, flexmock, interception, mock, mocking, rspec, stub, stubbing | no comments