Whether you are using mocks or stubs does not really make that big of a difference. But what does make a huge difference is using mocks or stubs on one hand, versus using fakes on the other hand.
Mocks/stubs and Fakes (using Fowler's terminology) make different amounts of assumptions about the world, and subsequently the choice of whether to use mocks/stubs or fakes has a real impact on what kind of testing you are doing:
- are you doing genuine, traditional, bona fide "unit testing", or
- are you taking shortcuts and mixing your unit testing with integration testing?
- are you testing against the interface? or
- are you testing against the implementation?
Mocks usually assume a lot: They assume that specific methods will be invoked, sometimes in a specific order, usually with very specific arguments, and they return very specific predetermined results in each call, which represent another whole new level of assumptions: they are the results that some programmer assumes that the real module would have returned for the given parameters and in its presumed state.
Stubs make somewhat fewer assumptions about the ways in which they are invoked, but they still assume a lot, both in terms of how they will be invoked, and in terms of how the real module would have responded. That's because stubs, just like mocks, are only built to respond to the specific usage pattern that is expected from the unit-under-test, when exercised by the specific test. Essentially, the use of stubs is equivalent to the use of mocks, their only difference being that stubs are usually hand-crafted, so even if you wanted to code more assumptions it would be a lot of work to do so, while mocks are usually created using some mocking framework which makes it easy to specify assumptions, so you are likely to specify more of them.
Fakes, on the other hand, tend to make as few assumptions as possible, and instead they try to offer a set of functionality that is as close as possible to the real thing. The ideal fake behaves in a way which is indistinguishable from the original, from the point of view of code which is exercising its public interface. (The differences are in their inner workings, and of course in what other subsystems they may or may not invoke in order to do their job.) As such, fakes usually do not depend on any particular usage pattern from any unit-under-test, exercised by any test.
From the above it should be clear that when you are using mocks or stubs you are making excessive amounts of assumptions about the inner workings of the unit under test, while when you are using fakes you are making no such assumptions: you don't care exactly how your unit-under-test is invoking your fake object, all you want to see whether the end result of their interaction is correct.
This in turn means that:
When you are using mocks or stubs you are doing traditional unit testing "by the book", and you are totally disregarding the advice which says "test against the interface, not against the implementation".
When you are using fakes you are not really doing unit testing "by the book", you are mixing it a bit with integration testing, (*) but you can in fact stick to testing against the interface instead of the implementation.
These are the facts, and you can use them to make your own educated choices. If you want my opinion, fakes are a far superior choice to mocks and stubs, because testing against the implementation is not a wise thing to be doing, whereas doing something by the book when there is no objective merit in doing so is just cargo-cult programming.
(*) I am saying "mixing it a bit with integration testing" in the sense that you are testing in integration with the fake, and you are deviating from the rule of unit testing which requires exercising your subsystem-under-test in complete isolation from its environment. With fakes, you do not anymore have complete control over every single detail of the interaction between your subsystem-under-test and its dependencies; you let the subsystem-under-test invoke whatever methods it needs on the fake, while the fake responds as faithfully to the original as possible. If the subsystem-under-test invokes a method on the fake that you did not expect to be invoked, or with parameter values that you did not expect, you will not necessarily know from this type of testing. This is exactly what happens in integration testing. And it is okay, because all you care about in integration testing is correctness of the end result of the interaction between your subsystem-under-test and its dependencies. In this case it just so happens that some or all of the dependencies are fakes.