We are a software consultancy based in Berlin, Germany. We deliver
high quality web apps in short timespans.

Upstream Agile GmbH

Using Mocha - A review

August 01, 2007 by alex

For aprox. 3 months we have been using mocha now. And we promised to get back to this subject when we passed our 2000th revision.

In this post I’d like to share some expirence we made while using mocha. But first a short introduction to mocha taken from the rubyforge page.

Mocha is a library for mocking and stubbing using a syntax like that of JMock, and SchMock. One of its main advantages is that it allows you to mock and stub methods on real (non-mock) classes and instances.

We started to use mocha on an existing test suite and changed our test code in place when we wrote a new test or had to update old ones. The transition was mostly painless, there were three things where we stumbled.

Avoid any_instance, we got some strange interactions with instances in other tests (Maybe changed with the 0.5 release, which is great btw.). You can get along very well without any_instance by stubbing .new. The second challenge was more the change in the way how to write tests. We were used to push all the required objects in the database, then run the code and examine the result afterwards.

def test_index_shows_logged_in_user_in_highscore
  5.times { create_user :game_points => 10 }
  u = create_logged_in_user :game_points => 3, :first_name => 'gamer'
  get :index
  assert_select '#hall_of_fame td', u.name
end

This example shows an old functional test. As you can see we hit the database hard. We built some helper methods like create_user to create often used database objects, but we didn’t think about stubbing so far.

With mocha in place, we could test the expected behavior, which must be expressed beforehand. We also had to build new test helpers to provide us with stubs instead of full fledged objects, or at least real objects with stubbed behavior.

def test_index_shows_logged_in_user_in_highscore
  user = new_logged_in_user :game_points => 3, :first_name => 'gamer'
  Game.expects(:all_time_rank_of_user).with(user).returns(1)
  get :index
  assert_select '#hall_of_fame td', user.name
end

This code tests the same as the test above but this time with mocha. This test (almost) doesn’t hit the database and is more readable with the mocha syntax.

The last thing we had to learn. Every unexpected call to a mock object will cause your test to fail. This is good in a way, because you lessen the risk to miss some dependency in your code. But in the beginning it was hard to find the origin for the missed call, because the type is always ‘Mock’, so that you get errors like the following: #.find(:first, :order => 'created_at DESC'}) - expected calls: 0, actual calls: 1 Similar expectations: find('11') find('11') </code> Only the call parameters and the stack trace can reveal the culprit.

If you get confused about all this mocking and stubbing. You can find a good explanation between the difference of mocking and stubbing and the different flavors in these slides from james mead

The reward for all the work are much faster running tests and a decoupled testsuite, because you can mock/stub dependencies.

Here some real figures.

Before: 424 unit tests with 637 assertions run in 319 seconds. 326 functional tests with 646 assertions run in 322 seconds

After: (still some potential) 659 unit tests with 1177 assertions run in 165 seconds. 574 functional tests with 1285 assertions run in 171 seconds.

The nubers speak for themselves 8)