Yep. That''s why most people who are uncomfortable with mocks are uncomfortable with mocks. In my experience mocks work best at well-defined, relatively stable interfaces to external(ish) services. Otherwise you''re testing the implementation, which makes refactoring difficult. But there are lots of people who use mocks all the time, so I''ll let them make the arguments in favor... Stubs and Fakes, however, can be more robust. Don''t paint all Test Doubles with the same brush! :-) --- Alex Chaffee - alex at stinky.com - http://alexch.github.com Stalk me: http://friendfeed.com/alexch | http://twitter.com/alexch | http://alexch.tumblr.com On Tue, Aug 25, 2009 at 10:12 AM, Denis Haskin<denis at haskinferguson.net> wrote:> This isn''t really an rspec question, but about mocks and stubs in general, > and maybe I''m looking for some best practices with rspec. > > I like mocks and stubs until code starts to change, and then I always get > frustrated because examples then seem so brittle and sensitive to > implementation details, which feels wrong to me. > > So a small example: this is in an example for a Discount model: > ????? @order.should_receive(:line_items).and_return(mock_model(LineItem, > :size => 2)) > (@order is a mock; I realize it maybe should just be a stub) > > The key expectation in this example is: > ????? @discount.should_not be_available(@order) > > But I changed the implementation of Discount#available? so that it calls > Order#num_products instead of Order#line_items.? My examples now fail. > > Why am I having trouble getting comfortable with this? > > Thanks, > > dwh > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
On 25 Aug 2009, at 18:12, Denis Haskin wrote:> The key expectation in this example is: > @discount.should_not be_available(@order) > But I changed the implementation of Discount#available? so that it > calls Order#num_products instead of Order#line_items. My examples > now fail. > Why am I having trouble getting comfortable with this?Your specification describes the interaction of Discount with the rest of the system; the idea of mocking is to nail down how a Discount object behaves in isolation from all of the other objects it interacts with. (And then, at a higher level, you have integration tests to check that all of the individual behaviours you''ve specified play nicely together.) If you change the nature of these interactions, for example by getting Discount to ask Order for a number instead of for a collection of line items, then you''re essentially changing the API between different parts of your application so your specs will and should fail. This is exactly the right amount of brittleness for the level the specs operate at. Your integration tests should still pass, indicating that the system as a whole still works, while your specs fail, indicating that the interactions between objects are not what you specified. Some of the time it does feel like you''re fixing up unnecessary breakage but it''s important for the specs to be able to reveal failures at this fine-grained level because these sorts of API changes aren''t always made deliberately, especially when you''re doing big refactorings. Note that your specs are only "brittle" in the sense that changes in the contracts between collaborating objects ("Discount expects Order to be able to return a collection of line items" changing to "Discount expects Order to be able to say how many products it consists of") require matching changes in those objects'' specifications. You can change the implementation of methods on Discount as much as you like -- by replacing poor algorithms with more efficient ones, for example -- and the specs won''t necessarily break unless your changes affect the interactions between Discount and "the outside world" of other objects. Cheers, -Tom