Wednesday, October 17, 2007

Mocking JRuby

Testing is one of the easy ways to sneak JRuby into your organization because it is easier to write tests in dynamic languages (especially mock object tests) and it isn't code that is deployed, so it eases the minds of the furniture police somewhat. Here is an example, adapted from Martin Fowler's essay on mock object testing.

In Martin's example, he creates Order and Warehouse classes (and a Warehouse interface), then writes tests with them in jMock. Here is one of those tests, rewritten in JRuby, using the Mocha mocking library:

require 'test/unit'
require 'rubygems'
require 'mocha'

require "java"
require "Warehouse.jar"
['OrderImpl', 'Order', 'Warehouse', 'WarehouseImpl'].each { |f|
eval "#{f} = com.nealford.conf.jmock.warehouse.#{f}"
}


class OrderInteractionTest < Test::Unit::TestCase
TALISKER = "Talisker"

def test_filling_removes_inventory_if_in_stock
order = OrderImpl.new(TALISKER, 50)
warehouse = Warehouse.new
warehouse.stubs(:hasInventory).with(TALISKER, 50).returns(true)
warehouse.stubs(:remove).with(TALISKER, 50)

order.fill(warehouse)
assert order.is_filled
end

def test_filling_does_not_remove_if_not_enough_in_stock
order = OrderImpl.new(TALISKER, 51)
warehouse = Warehouse.new
warehouse.stubs(:hasInventory).returns(false)
order.fill(warehouse)

assert !order.is_filled
end

end

The order object is a pure Java object, tested with a mocked out version of the Warehouse interface. Now, this isn't exactly what Martin wrote in in excellent essay about mocks vs. stubs, but the intent is preserved in this example.

Most of the really crufty stuff in mock objects in Java is getting the compiler to agree with you that the mock object type is what you want it to be, and of course that problem goes away in JRuby. Also notice that I can create the mock object directly off the interface. Because JRuby creates proxy objects for the Java classes, you can create a mock from the interface directly by calling the new method on it. The other cool thing about this example is the require "Warehouse.jar" line at the top. JRuby allows you to require a JAR file, which gives you access to the Java classes within it.

4 comments:

Nat Pryce said...

What about refactoring? The biggest complaint about jMock 1 (now addressed in jMock 2) was that expectations were difficult to refactor. Doesn't this approach of using JRuby extend that problem to the entire test suite?

Neal Ford said...

Refactoring is generally weaker in Ruby (although getting better, I doubt it will ever be as good as Java or other static languages). Thus, this code is harder to refactor than the corresponding jMock code. But, you can't have everything, so you get to pick: cleaner, easier mocks with a dynamic language or refactorability with jMock.

Nat Pryce said...

I'm not sure about cleaner, easier mocks. The expectations in the code above are more cryptic than those in jMock 2.

I find that surprising because Ruby's blocks would make it much easier to use real method calls to describe expectations, compared to the hoops we had to jump through to write the jMock 2 API.

Steve Freeman said...

Seconding Nat (of course).

The most relevant part of a dynamic language is the doesNotUnderstand/missingMethod (or whatever it is in Ruby) hook. Tim Mackinnon's Smalltalk mock library is a model of brevity because of the use he could make of blocks.