Dumb Moments
April 17th, 2007
As a developer you face many situations where things will just not work, then you have a revelation that points to something so simple that you want to slap yourself.
I just had one today so profound I thought I’d share it with the public.
I wanted to have fun on my day off, but instead of going outside on the beautiful day, or doing some thing active like any sane person, I decided to play with FlexMock and Mocha.
I had my fun with FlexMock and was moving onto Mocha, when I encountered a strange yet irritating problem.
require "rubygems"
require "test/unit"
require "mocha"
require "others"
class OthersTest < Test::Unit::TestCase
def test_foo
person = mock("person")
end
end
I kept getting
E
Finished in 0.000419 seconds.
1) Error:
test_foo(OthersTest):
NoMethodError: undefined method `mock' for #<OthersTest:0x110b5c4>
So I went over my gems to make sure they were installed correctly. Even grabbed a sample test file and that resulted in the same error.
So I edited themocha.rb in the gem lib directory and added one of my Yoda-like scientific debugging techniques
puts "in here"
Still having problems. So I tweaked the test case a little to
require "rubygems"
require "test/unit"
require_gem "mocha"
require "others"
Success! ... if you ignore the Deprecation Warning from rubygems. Warning: require_gem is obsolete. Use gem instead. Being a perfectionist, that irked me.
Wondering how this gem could hate me in particular yet spare everyone else it’s wrath, I stepped back and noticed the forest for the trees. The test’s filename was mocha.rb. An innocuous file name from a sleepy eyed programmer at 8 a.m. in the morning. (More support that coders shouldn’t touch a keyboard before 10 a.m.)
The original require "mocha" was seeming to ignore the gem because it was named the same, and of course I plopped the sample file in the same directory as the uglified test, so it wouldn’t grab the gem.
Why do Dumb Moments always last at least one hour?
Hardstub
April 10th, 2007
Previously I spoke about Hardmock, a mocking library. Most of the time I try to develop using Dependency Injection or at least keep instantiation of objects together. However, sometimes the real world kicks in and I can’t or don’t have time to refactor the code.
Enter Hardstub. This will replace the definition of a class with a mock. Again, I’m not promoting testing in this manner, but this may open possibilities that may have been previously discounted.
class Person
def initialize(car)
@car = car
@wallet = Wallet.new
end
def go_to_movies
@car.drive_to_movies
@wallet.pay_for_ticket("10.00")
@car.drive_home
end
end
class Wallet
# not fully implemented yet
end
...
# in tests
def setup
stub(Wallet)
create_mock :car_mock, :wallet_mock
end
def teardown
# this is needed
revert_stubs
end
def test_go_to_movies
Wallet.expects.new.returns(@wallet_mock)
@car_mock.expects.drive_to_movies
@wallet_mock.expects.pay_for_ticket("10.00")
@car_mock.expects.drive_home
person = Person.new(@car_mock)
person.go_to_movies
end
Currently, Hardstub isn’t smart enough to automatically revert the stubs, so make sure to call revert_stubs in teardown. Another tip, don’t stub very common classes such as File or Dir.
Update 4/11/07
There’s currently an issue that is keeping Hardmock from auto verifying, so until this is fixed, a work around is to put this in your teardown.
def teardown
verify_mocks
ensure
revert_stubs
# Other teardown code
end
Hardmock
April 9th, 2007
Most ruby developers who use mock libraries are familiar with either Mocha or Flex Mock. However not many are familiar with the mock library that I use, Hardmock.
Hardmock takes mocking one step further to assert order between mocks, much like the Java mocking library EasyMock
class Person
def initialize(car, wallet)
@car = car
@wallet = wallet
end
def go_to_movies
@car.drive_to_movies
@wallet.pay_for_ticket("10.00")
@car.drive_home
end
end
...
# in tests
def setup
create_mocks :car_mock, :wallet_mock
@person = Person.new(@car_mock, @wallet_mock)
end
# This test will pass
def test_go_to_movies
@car_mock.expects.drive_to_movies
@wallet_mock.expects.pay_for_ticket("10.00")
@car_mock.expects.drive_home
@person.go_to_movies
end
# This test will fail since we
# drive home before we paid for the ticket
def test_go_to_movies_failure
@car_mock.expects.drive_to_movies
@car_mock.expects.drive_home
@wallet_mock.expects.pay_for_ticket("10.00")
@person.go_to_movies
end
Another one of my favorite perks of Hardmock is the open assertions of parameters. For the most part, parameters are expected to equal what is given in the initial assertions, however sometimes there’s complications on asserting the parameters given.
def test_allow_any_money_for_ticket
@car_mock.expects.drive_to_movies
@wallet_mock.expects.pay_for_ticket do | amount |
# this will be executed when "pay_for_ticket" is called on the mock
# Only allow dollar amounts
assert_match(/^\d+\.\d{2}$/, amount)
# of course the last value in this block
# with be returned from the mock to the caller
"the ticket"
end
@car_mock.expects.drive_home
@person.go_to_movies
end
Hardmock was written by the solid developers at Atomic Object
Depth Merge Update
April 9th, 2007
There was a small bug with Depth Merge when merging nils without deleting them. Behavior should now be the same as the regular merge.