Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assert that no method is called on a mock

Tags:

ruby

rspec

rspec2

I am writing tests with Rspec2 using the Flexmock mocking framework. I expect one of my methods to cache results and want to verify this with my mock.

describe SomeClass do
  before do
    @mock = flexmock()
  end

  after do
    @mock.flexmock_verify()
  end

  it "method caches results"
    c = SomeClass.new(@mock)
    c.method
    @mock.should_receive(:expensive_method).never
    c.method.should == ['A']
  end
end

This works reasonable well if I want to make sure that :expensive_method is never called. However, I am expecting my class to be able to do :method without calling anything on the passed in (mock) class. Is there a way to express this?

Background: In my case, the injected class performs expensive operations and therefore results should be cached for equal queries.

Update
Thanks for the suggestions so far, maybe I'm just assuming wrong things or maybe what I want doesn't even make sense. The way I implemented my caching is by holding a class variable in SomeClass and add to that in the :method:

def SomeClass
  @@cache_map = {}

  def method
    # extract key
    return @@cache_map[key] if @@cache_map.has_key?(key)
    # call :expensive_method to get result
    @@cache_map[key] = result
    return result
  end
end

Now, in order to test the correct caching, I have to call :extensive_method at least once to load the cache. I like David Chelimsky's solution, but it isn't answering my original intent, which is: Test that after the first call to SomeClass.method the injected class is never called again (neither :expensive_method nor anything else).

like image 706
jhwist Avatar asked Dec 29 '22 04:12

jhwist


2 Answers

The conventional way to specify caching is to say @mock.should_receive(:expensive_method).once before invoking method at all, and then invoke the method that calls it twice.

I also like to use two examples for this: one to specify how it uses the data, one to specify that it only retrieves the data once:

describe Client do
  let(:service) { flexmock() }
  let(:client)  { Client.new(service) }

  it "uses data retrieved from service" do
    service.stub(:expensive_method).and_return('some data')
    client.method.should eq('some data')
  end

  it "only retrieves the data once" do
    service.should_receive(:expensive_method).once
    client.method
    client.method
  end
end

Also, you don't need to call flexmock_verify, as that happens automatically.

like image 178
David Chelimsky Avatar answered Jan 11 '23 10:01

David Chelimsky


Surely not setting any expectations on the mocked object will achieve this. Any call made to the mock will result in an unexpected call failure (I'll admit I've never used Flexmock but every other mocking frame work I've used works this way).

Unfortunately there isn't really any way to make this explicit in the test. Maybe change the test name to indicate it, maybe something like "method uses cached result without calling mocked object".

like image 43
Jonathan Avatar answered Jan 11 '23 12:01

Jonathan