Mochaのstub_everythingは強力

Jay Fields' Thoughts: Testing: Mocha's stub_everything method

stub_everythingの強力さについて。

stub_everythingメソッドは、どんなメソッドを呼び出してもnilを返すオブジェクトを生成する。なんていうか、幽霊…みたいな、透明人間みたいなオブジェクト。
stub(:meth => retval)同様「stub_everything(:meth => retval)」のように特定のメソッド呼び出しの返り値を設定することもできる。stubだと特定のメソッドを偽装するだけだが、stub_everythingはその他のメソッドは素通りするようになる。

こんなコードを例とする。

class ReservationService
  # implementation
end
class MaidService
  # implementation
end
class VipService
  # implementation
end

class HotelRoom
  def book_for(customer)
    reservation = ReservationService.reserve_for(customer, self)
    MaidService.notify(reservation) if reservation.tomorrow?
    VipService.notify(reservation) if reservation.for_vip?
    reservation.confirmation_number
  end
end

で、stub_everythingの実例。

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

class HotelRoomTests < Test::Unit::TestCase
  def test_book_for_reserves_via_ReservationService
    room = HotelRoom.new
    ReservationService.expects(:reserve_for).with(:customer, room).returns(stub_everyt
    room.book_for(:customer)
  end
end

room.book_for(:customer)を呼んだとき、「ReservationService.reserve_for(:customer, room)」が呼ばれていることをテストする。
「ReservationService.reserve_for(:customer, room)」の返り値をstub_everythingにすると、そのオブジェクトにたいする一切の操作を無視できる。

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

class HotelRoomTests < Test::Unit::TestCase
  def test_book_for_reserves_via_ReservationService
    reservation = stub_everything(:confirmation_number => "confirmation number")
    ReservationService.stubs(:reserve_for).returns(reservation)
    assert_equal "confirmation number", HotelRoom.new.book_for(:customer)
  end
end

返り値がconfirmation numberであることをテストする。
「reservation.confirmation_number(*args) == "confirmation number"」かつ「その他のメソッドはnilを返す」オブジェクトreservationを偽装する。
「ReservationService.reserve_for(*args)」の返り値をその偽装オブジェクトにする。

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

class HotelTests < Test::Unit::TestCase
  def test_name_is_set_from_options
    hotel = stub_everything
    hotel.expects(:name=).with "Marriott"
    Hotel.stubs(:new).returns hotel
    Hotel.parse("Marriott||")
  end
end

class Hotel
  def self.parse(attributes)
    hotel = self.new
    hotel.name, hotel.location, hotel.capacity = attributes.split("|")
    hotel
  end
end

これはHotelオブジェクトすら偽装している、ガチガチのスタブだ。ここでテストするのは、「hotel.name == "Marriott"」であることのみだ。「hotel.location」や「hotel.capacity」は不要なのであえてスタブにしているのだ。
そこまでやるかと思うほどの徹底ぶり。