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:
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?
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.
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.
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.
Post a Comment