Brian Takita's blog
Introducing RR
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 http://xunitpatterns.com/Test2e.html and Martin Fowler's article, http://martinfowler.com/articles/mocksArentStubs.html. 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.
Column Edit Mode in VI
I've found that typing in column mode to be very useful when using editors like IntelliJ or TextMate. VI also has a column edit mode, that is a little tricky to use.
To use it, press:
- Ctrl + V to go into column mode
- Select the columns and rows where you want to enter your text
- Shift + i to go into insert mode in column mode
- Type in the text you want to enter. Dont be discouraged by the fact that only the first row is changed.
- Esc to apply your change (or alternately Ctrl+c)
You will now see your changed applied.
Get Rake to always show the error stack trace for your project
Tired of rake hiding your error stack trace?
rake aborted!
Build failed
(See full trace by running task with --trace)
You can have rake always show your error stack trace by going into your project's Rakefile and setting:
Rake.application.options.trace = true
Now you never need to worry about passing --trace again.







