3 ms·
It seems to me that a Fake is really a Mock, and Mocks became this unholy dynamic framework stuff. I'm not a fan of dynamic mocks where the test code has the mo
by mathgladiator 6y ago
It seems to me that a Fake is really a Mock, and Mocks became this unholy dynamic framework stuff. I'm not a fan of dynamic mocks where the test code has the mocked implementation because it is super brittle. Instead, a "fake" (i.e. a mock as I understood them) is an alternatively implementation of an interface.
There are a lot of really good "fakes" out there. Fake file system which lets you simulate disk failures. Fake time and randomness to introduce determinism. Every API over network can have a fake. These are all good things to fake out as you can simulate the appropriate failures which are almost impossible to do via integration, acceptance, or E2E tests.
- viraptor 6y agoThe problem with those alternative implementations is that they turn your unit tests into expensive and complicated integration tests. In a complex system getting a socket into the state you're interested in can be many times harder than the test itself. "Magic" mocks make it cheap and easy.
- chriswarbo 6y agoThere'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.
- whalesalad 6y agoAgreed. This is a naming/semantics/nomenclature issue in the testing world at large. The lines have all been blurred and words have different meanings across different language ecosystems.
- jsmith45 6y agoThe most complete nomenclature I have seen is that used by Marin Fowler (which he attributes to Gerard Meszaros). I like it because it gives a fairly decent taxonomy of test doubles, and thus makes it possible to easily describe some of the common types, and for example, suggest that some other type might be better suited in some situation. I summarize it in my own words below: Dummy: Trivial implementation that might do nothing, return some default value, or throw exception if called. These are intended to be used only when an object gets passed but never used. Fake: Have a working implementation, but takes some shortcut that makes them unsuitable for use in production. These shortcuts can potentially be pretty extreme, like a fake sales tax service that just uses a flat percentage for everything, an image watermarker that just returns the original image, etc. Basically if one could substitute the original implementation for this, still be spin up and use the whole app, without worrying about it crashing/misbehaving (such as hitting some unimplemented case or fixed return data causing map collisions etc), then it is a fake. Stub: Harded implementation. Is not fully functional, and designed to have correct or usuable responses only for scenarios that the test will care about. (Sometimes designed to take into account a small group of tests). Spy: A spy is an implementation that records some information about how it is used. It could be a stub or a fake. IT could also be a decorator patten wrapper around some other implementation. (Fowler incorrectly oversimplifies this by defining it to be a stub implementation). The recorded information can later be accessed to verify say the number of emails the code being tested tried to send. Mock: An object that gets preprogrammed with expectations about how it will be called (and what it should return). Then after running the test, it is asked to confirm if the calls made upon it match the pre-programmed ones. One can pretty clearly see that this is a bit different than the others, in that it contains special verification logic. ----- Either a spy or a mock is needed when the behavior being tested is not visible as either part of the return value, or as part of the state of some object after the test is completed. Those cases require "behavior verification", as opposed to "state verification". State verification might check some object's propeties, or query the fake database to see if the resulting state is as expected, while behavior verification. The need for behavior verification can be common in certain "command" style apis. For example, if a dependency's purpose is to send emails, then it is quite possible that there is no state that remains (within the program) to verify if it got called. So the best that is possible is to verify that the dependency got called (or did not get called) as expected. (Don't take the state vs behavior distinction too literally. Obviously both Mocks and Spies store recoded information as state. The important concept is that the normal implementations would not include this state.) --- The article in question on which the above is all loosely based is: https://martinfowler.com/articles/mocksArentStubs.html https://martinfowler.com/articles/mocksArentStubs.html