3 ms·
There's nothing preventing a fake implementation of interface Foo from also having extra functionality, like controlling it's initial state. In the case of sock
by chriswarbo 6y ago
There's nothing preventing a fake implementation of interface Foo from also having extra functionality, like controlling it's initial state. In the case of sockets, we might have a FakeSocket which implements methods Socket::close, Socket::read, etc. but also provides methods like FakeSocket::appendToBuffer, FakeSocket::withState, etc.
The boundary between such fakes and mocks is obviously fuzzy: as we override more of a fake's behaviour we approach a mock, as we make a mock more functionally-complete we approach a fake.
Mocks (in this sense) are most useful when we want a lot of control over a very simple interaction; e.g. having Socket::read return a particular error state. We don't need a whole fake implementation for that, and indeed it's probably easier to just specify a mock with only those parts (and nothing else).
In my experience such situations are rare, since they tend to result from over-complication in the system under test, which makes it hard to test. Mocks are a useful crutch in those situations, but it's often better to refactor the code such that mocks aren't needed.
For example, whenever we create a mock that returns some canned response, that's a hint that our 'service object' is probably just a (possibly lazy) wrapper around that response. Any code that processes such responses can often be refactored into an ordinary function/method which accepts such response data as an argument. Sending test data into such functions is trivial (just call the function on the test data), and we can delete whatever wrapping/unwrapping boilerplate was previously littering the code.
When our mocks are more complicated that just spitting out canned responses (e.g. there's some temporal dependency between method calls, and correlations between their responses), those are exactly the things that fake implementations are for.