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 the mocha.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.

Original Article