I’m pleased to introduce a new Test Double (or mock) framework named RR, which is short for Double Ruby.
Why a Double framework and not a Mock framework?
A mock is a type of test double. Since RR supports mocks, stubs, and proxies, it makes sense to refer to RR as a double framework. The proxy is a new usage pattern that I will introduce later in this article, and in more detail in future articles.
Unfortunately, the terminology over doubles has been contradictory depending on the framework. RR’s terminology tries to be as faithful as possible to Gerald Meszaros’ definition of test doubles. You can read more about test doubles in XUnit Test Patterns and Martin Fowler’s article, Mocks aren’t Stubs. Regretfully, this does mean that RR will have slightly different terminology than other double frameworks.
How does RR compare to other Mock frameworks?
Most double frameworks focus mainly on mocks (hence the categorization “mock framework”). RR’s focus is on enabling more double test patterns in a terse and readable syntax.
RR also does not have dedicated mock objects. It primarily uses the technique called ‘double injection’. Names that other frameworks use are ‘stub injection’, ‘mock object injection’, ‘partial mocking’, or ‘stubbing’. The term I’ll use for this is a double injection, since one or many doubles are being injected into an object’s method.
I’ll use trivial Rails examples to highlight the syntactical differences between RR, Mocha, Rspec’s mocks, and Flexmock. They may or may not be appropriate situations for mocks. The right situations for mocks is an entirely different discussion.
If there is better way to do any of the examples, please post a comment and I will gladly replace it.
Mocks
Here are the ways to mock the User.find method. The expectation is the User class object will receive a call to #find with the argument ’99′ once and will return the object represented by the variable user.
RR
mock(User).find('99') { user }
Mocha
User.expects(:find).with('99').returns(user)
spec/mocks
User.should_receive(:find).with('99').and_return(user)
Flexmock
flexstub(User).should_receive(:find).with('99').and_return(user).once
Stubs
Here are the ways to stub the User.find method. When the User class object receives a call to find with the argument ’99′ it will return user1. When User receives find with any other arg, it returns user2.
RR
stub(User).find('99') { user1 }
stub(User).find { user2 }
Mocha
User.stubs(:find).with(anything).returns(2)
User.stubs(:find).with('99').returns(1)
spec/mocks
users = {
'99' => user1,
'default' => user2
}
User.stub!(:find).and_return do |id|
users[id] || users['default']
end
Flexmock
users = {
'99' => user1,
'default' => user2
}
flexstub(User).should_receive(:find).and_return do |id|
users[id] || users['default']
end
Proxy
A proxy used with a mock or stub causes the real method to be called. Expectations can be placed on the invocation and the return value can be intercepted. The main rationales are test clarity and you can ensure that the methods are being called correctly, even after you refactor your code. I will delve more into proxies and their usage patterns in my next article.
Mock Proxy
The following examples set an expectation that User.find(’99′) will be called once. The actual user is returned.
RR
mock.proxy(User).find('99')
Mocha
You cannot implement this in Mocha. You can do an approximation in this situation however. This technique is not always the solution you need, though.
user = User.find('99')
User.expects(:find).with('99').returns(user)
spec/mocks
find_method = User.method(:find)
User.should_receive(:find).with('99').and_return(&find_method)
Flexmock
find_method = User.method(:find)
User.should_receive(:find).with('99').and_return(&find_method)
Stub Proxy
The following examples intercept the return value of User.find(’99′) and stub out valid? to return false.
RR
stub.proxy(User).find('99') do |user|
stub(user).valid? {false}
user
end
Mocha
Again, this is an approximation, since you cannot use proxies in Mocha.
user = User.find('99')
user.stubs(:valid?).returns(false)
User.stubs(:find).with('99').returns(user)
spec/mocks
find_method = User.method(:find)
User.stub!(:find).with('99').and_return do |id|
user = find_method.call(id)
user.stub!(:valid?).and_return(false)
user
end
Flexmock
find_method = User.method(:find)
flexstub(User).should_receive(:find).with('99').and_return do |id|
user = find_method.call(id)
flexstub(user).should_receive(:valid?).and_return(false)
user
end
instance_of
instance_of is method sugar than allows you to mock or stub instances of a particular class. The following examples mock instances of User to expect valid? with no arguments to be called once and return false.
RR
mock.instance_of(User).valid? {false}
Mocha
User.any_instance.expects(:valid?).returns(false)
spec/mocks
new_method = User.method(:new)
User.stub!(:new).and_return do |*args|
user = new_method.call(*args)
user.should_receive(:valid?).and_return(false)
user
end
Flexmock
new_method = User.method(:new)
flexstub(User).should_receive(:new).and_return do |*args|
user = new_method.call(*args)
flexmock(user).should_receive(:valid?).and_return(false)
user
end
More to come
This concludes the introduction to RR. RR enables some techniques, like proxying, that will make your tests clearer and less brittle. In the next article I will describe into patterns and techniques that will make mocks a more feasible tool for more situations.
The introduction of different mocking/stubbing strategies that RR implements is quite refreshing and I can certainly appreciate its terseness. I’m looking forward to your next post and am particularly interested in how RR can be used to stub out instances of unimplemented classes.
January 1, 2008 at 4:21 am
How do the last two examples for `spec/mocks` and `flexmock` stub out the `valid?` call on any instance?
January 1, 2008 at 8:38 am
Kamal: Thank you. I fixed the code samples.
The mocking needs to happen inside of the block.
January 1, 2008 at 10:24 pm
What if the method expects a block?
In the example you have mocking and stubbing specifying their return values in the block:
mock(User).find(’99′) { user }
stub(User).find(’99′) { user1 }
stub(User).find { user2 }
but, how would I stub a call to each, select, collect, etc.
array = [1,2]
mock(array).select …?
Blocks have been a pain point for Mocha also, so I’m curious what the RR plan is.
January 1, 2008 at 10:51 pm
Also, the mocha stubbing example can be done (unless I’m missing some part of the example)
require ‘rubygems’
require ‘mocha’
require ‘dust’
require ‘test/unit’
unit_tests do
test “changing stub behavior” do
User = Class.new
User.stubs(:find).with(anything).returns(2)
User.stubs(:find).with(’99′).returns(1)
assert_equal 1, User.find(’99′)
assert_equal 2, User.find
end
end
January 1, 2008 at 10:52 pm
Sorry, better formatting this time (hopefully):
require ‘rubygems’
require ‘mocha’
require ‘dust’
require ‘test/unit’
unit_tests do
test “changing stub behavior” do
User = Class.new
User.stubs(:find).with(anything).returns(2)
User.stubs(:find).with(’99′).returns(1)
assert_equal 1, User.find(’99′)
assert_equal 2, User.find
end
end
January 1, 2008 at 10:54 pm
Jay: RR provide the yields method. In addition, the return block can also can also take the block convented to a Proc as the last argument (like spec/mocks).
The array is a strange example, because in most situations you can depend on the real implementation for its methods.
Lets say you do want to have something on the yield.
Here is an example using yields:
array = [1, 2]
mock.proxy(array).select.yields(2)
return_value = array.select do |value|
value == 2
end
assert_equal [2], return_value
Of course its limited to one call. It will not iterate. The block only gets called once. This example demonstrates:
array = [1, 2]
mock.proxy(array).select.yields(1)
return_value = array.select do |value|
value == 2
end
assert_equal [], return_value
You can also use yields to set the return value.
mock(array).select.yields(2) {[2]}
Here is an example using the return value block:
array = [1, 2]
dup_array = array.dup
mock(array).select do |block|
current_iteration_value = dup_array.shift
dup_array.select(&block)
end
return_value = array.select do |value|
value == 2
end
assert_equal [2], return_value
Blocks are tough to mock. What sort of requirements do you have?
January 2, 2008 at 3:19 am
Jay: Thanks for the Mocha update. I’ll fix the example.
January 2, 2008 at 3:20 am
This looks great. Without having known the terminology, I’ve been wishing that Mocha could setup an expectation that still passed the method call back to the original object. Proxying will definitely come in handy.
January 3, 2008 at 12:27 am
Yeah, the all in one line proxying would be handy, but in Mocha specs I’ve just added one more line aliasing a method before stubbing it, and then returning the call on that, as needed (where the proposed preset reference solution wasn’t appropriate). It might not be quite as flexible as true proxying, but since I’m mocking everything out anyways, the fixed values are fine and it’s worked well enough when I’ve needed.
January 4, 2008 at 11:18 pm
Tammer: Can you provide a code example?
January 7, 2008 at 5:35 pm
Hello Brian.
Today I’ve updated rails to rev 9248 and rr mocks for dynamic find_by methods in my specs ceased to work, instead giving the message
undefined method `find_by_id’ for class `Class’
/usr/lib/ruby/gems/1.8/gems/rr-0.4.8/lib/rr/double_injection.rb:17:in `alias_method’
/usr/lib/ruby/gems/1.8/gems/rr-0.4.8/lib/rr/double_injection.rb:17:in `__send__’
/usr/lib/ruby/gems/1.8/gems/rr-0.4.8/lib/rr/double_injection.rb:17:in `initialize’
….
Could you please look into it ? Sorry if this page isn’t appropriate place to report rr bugs, I haven’t found links to bugtrackers here.
Thanks, Sergey.
May 20, 2008 at 11:25 am
Sergey, thanks for the bug report.
The current bugtracker is at
http://rubyforge.org/tracker/?atid=14084&group_id=3656&func=browse
I’ll set up a rubyforge project for RR.
June 18, 2008 at 2:57 pm
Sergey, unfortunately I’m not able to reproduce this issue on Rails 2.1.0. Can you paste your test on http://pastie.caboo.se?
Thanks,
Brian
June 19, 2008 at 6:22 am
How do I call the actual method (or any method for that matter) on the class I’m trying to stub/mock/proxy?
Example:
`
class MyObj
def self.meth1; end
def self.meth2; end
end
mock.proxy(MyObj).meth1 { meth2 }
`
Also what if I want to proxy a method but so that the mocked proxy is called FIRST, which would allow me to intercept the call before it’s being made. The current mock.proxy() seems to call the original method first and then the proxied code. If I’d want the proxy to be called first, and perhaps decide in the proxy block if the real method should be called or not..how do I do that?
Thanks..
September 25, 2008 at 1:11 pm
@James: Both use cases are interesting things that I have yet not considered.
The block is not instance_exec’d, so:
mock.proxy(MyObj).meth1 { meth2 }would not work.
Here is a way to solve it.
mock.proxy(MyObj).meth1 { MyObj.meth2 }Perhaps it should be instance_exec’d. I’ll play around with it.
Regarding the method interception, you could do the following:
I’d be willing to add a more explicit hook if a nice api can be found.
Maybe something like:
mock.proxy(MyObj).meth1.before {# do something}.after {# This is the return value}Such a change would be additive, so:
mock.proxy(MyObj).meth1 {# This is the return value}would still work.
September 30, 2008 at 7:48 pm
Great library thanks!
Have a bit of a problem, when the unit test in question defines the “teardown” method, none of the RR related assertions work. Is this normal?
For example, the following test “fails” correctly:
class MunnyTest < ActiveSupport::TestCase
include RR::Adapters::TestUnit
test “testing” do
x = Object.new
mock(x).to_s { ‘foobar’ }
assert_equal 1,1
end
end
But the following does not (it “passes”):
class MunnyTest < ActiveSupport::TestCase
include RR::Adapters::TestUnit
def teardown
super
end
test “testing” do
x = Object.new
mock(x).to_s { ‘foobar’ }
assert_equal 1,1
end
end
November 2, 2009 at 12:05 am