Has anyone tried to mock whole classes (instead of mocking only the objects)? Classes are, like everything else in Ruby, just objects. This allows us to mock them just like we would mock any other object. I have been working on a Rails application (that will be shared with you as soon as I get it translated, I promise) in which I needed to do exactly that. The class I wanted to mock is responsible for communicating to my back-end database and fetching the appropriate objects (instances of itself). The object-finding functionality is implemented as class level methods. If I need the N latest headlines from a reporter named 'johnson', I just need to call `Headline.latest(N, "johnson"). If I need to test a class that needs to do a Headline.latest call as part of its job, I don't want to populate the database with real data (and slow down my tests as I wait for a connection) because I mostly trust that Headline works. It has its own tests to assure that. So I mock the Headline class to make sure my class under test makes the correct calls. I have come up with a simple way to that in Ruby and I am mostly satisfied with the results, but I would like to get some feedback from the community. The source is here: ---- class CacheReporter < MotiroReporter def initialize(headlines_source=Headline) @headlines_source = headlines_source end def latest_headlines return @headlines_source.latest(3, 'mail_list') end end class CacheReporterTest < Test::Unit::TestCase def test_reads_from_database FlexMock.use do |mock_headline_class| mock_headline_class.should_receive(:latest). with(3, 'mail_list'). once reporter = CacheReporter.new(mock_headline_class) reporter.latest_headlines end end end ---- The main change here can be seen on the CacheReporter constructor. If I were not testing, it wouldn't even be written, I would just use the Headline class wherever I wanted. But instead of directly using the Headline class inside its methods, it receives it on the constructor. This is what allows us to mock the behavior. Has anyone done anything similar? Can the code be made simpler? Cheers, Thiago A.
on 2006-04-03 17:40
on 2006-04-04 00:59
On Apr 3, 2006, at 6:37 AM, Thiago A. wrote: > Has anyone tried to mock whole classes (instead of mocking only the > objects)? I've found it more pain than its worth because I change the implementation then have to go change all my mocks. No fun. > 'johnson', I just need to call `Headline.latest(N, "johnson"). When I need that kind of behavior I tend to do something like this: require 'flickr' class Flickr attr_accessor :responses, :uris def open(uri) @uris << uri yield StringIO.new(@responses.shift) end end I'm using open-uri so I inject a new open into my class that shadows Kernel's open. My tests prime it with a bunch of responses and when I call open I just record the URI and return the in-order response. For my test I check that the right URIs were accessed and the right data came back. Here's a test: class FlickrTest < Test::Unit::TestCase def setup @flickr = Flickr.new 'API_KEY' @flickr.responses =  @flickr.uris =  end def test_find_email @flickr.responses << <<-EOF <?xml version="1.0" encoding="utf-8" ?> <rsp stat="ok"> <user id="50178138@N00" nsid="50178138@N00"> <username>drbrain</username> </user> </rsp> EOF nsid = @flickr.find_email :find_email => 'email@example.com' assert_equal 1, @flickr.uris.length assert_equal 'http://flickr.com/services/rest/? api_key=API_KEY&find_email=drbrain% 40segment7.net&method=flickr.people.findByEmail', @flickr.uris.first assert_equal '50178138@N00', nsid end end -- Eric H. - firstname.lastname@example.org - http://blog.segment7.net This implementation is HODEL-HASH-9600 compliant http://trackmap.robotcoop.com