Don't Use Mocks
joeblu.com
joeblu.com
Having a title with a blanket statement like "Don't Use Mocks" is either plain wrong or clickbait. In this case, it seems it is both wrong and clickbait. Such blanket statements and clickbaits specifically trigger people to get into a long-winded debate (ironically, my own comment here is a case in point) about something that is obvious and otherwise uncontroversial.
Both are substitutes for an expensive or unavailable component.
Maybe the fake is more dynamic than the mock?
The point seems moot.
https://blog.cleancoder.com/uncle-bob/2014/05/14/TheLittleMo...
A mock is a cache for a desired return value (which is hopefully what the real interface would correctly return).
Edit: actually sethammons link is great
Fake is just a simple implementation, like in-memory db to stand-in for a real one. In the optimal case, provided by library authors.
But I personally think that instead of spending the time to write a fake, its better to just spend the time writing actual integration test with the real dependency (eg: just run the db in docker or something)
or if you don't want to spend that time, then just record the interaction (eg: db calls) and create "throwaway stubs". Use this stubs as long as they are relevant, and then generate new ones as your code grows. Save time "writing" any mocks, and you don't tend to couple too much with your mocks :P
That creates unit tests that take ages to run - and fail surprisingly in hard to debug ways.
There are scalability limits to this but for most cases it works extremely well and is fast.
A mock is a set of fixed data; sometimes people even load these from YAML documents and the like. They're often re-used for different tests.
A fake is a "fake" object created when needed, with the parameters you need.
Mock:
user = load_user_mock()
article = load_article_mock()
run_test(user, article)
Fake: user = newUser(Name: "foo", Email: "foo@example.com") # Or generate random data
article = newArticle(User: user, Title: "bar")
run_test(user, article)
The difference is somewhat subtle, but I often find mocks very inconvenient because you can't "just" change one without lots of stuff falling over. It's also much harder to do something "special" with them for that one test.What this article did not do was to describe the trade-off: what you give up by creating a more complicated "fake" (to use the author's term).
I am confused, wouldn't using simpler and "broader" tests miss test coverage on e.g. specific error handlers?
Better tests would assert some kind of higher level properties that are much less likely to change. This often involves making the dependencies you inject during testing some more complete simulations of real implementations. Takes more up front effort to implement them, but can often be re-used between many tests.
Tests on one hand help you modify software over time, but on the other hand increase maintenance burden. Being good at testing doesn't mean just writing lots of tests, but being good at judging the value of a test vs the cost of having it, which is very context dependent in itself.
That change would still be the same amount of maintenance (if the underlying implementation changes, the mock will have to be updated to reflect that as well) but the test will communicate more clearly what the intention of the interaction is.
At higher levels, the up front effort is higher but most of that work that is much more reusable - reusable across implementations and different languages, even.
With tools like playwright and MITM proxy, the state of the art for these highly reusable tools has moved forward considerably in the last 5 years. I can now "TDD" everything in a way that actually makes sense on some projects - even a tweak in CSS (via snapshot driven development).
Meanwhile CPU power has also kept accelerating to the point that hermetic end to end tests that were unbearably slow can now be run on a laptop in seconds. Entire suites that used to take hours on CI can be trivially parallelized and run in minutes instead.
I'll go further. These tests are worse than useless; they're harmful.
- They're the very definition of testing an implementation
- You will wasted a ton of time updating them when your implementation changes
- They give a false sense of security/productivity
Of course you don't want to have to use real hardware for these tests, especially because with some devices incorrect power-up sequencing can cause permanent damage. So you write mocks for the various hardware functions and test the driver with those, and use their counters & the test harness's logging to assert that the sequencing and timing is correct.
[1] https://www.intel.com/content/dam/www/public/us/en/documents...
There are the pushing-the-edge-of-excellence folks, constantly refining methods and approaches, who tend to be passionate about the holistic benefits of testing.
And there are the folks who write convoluted tests that, when you dig into them, just confirm that String's .equals() works OK. DAO/database tests which have mocking going on to the point where you're faking a database response of "hello", say, and then checking "expected response == hello". Then yay our test passes!!
Personally I feel our energy should somehow be spent collectively on getting the latter group to just not write total garbage, and that the bar for "good enough" is really not that high, after which we get into diminishing-returns territory. And I think, personally, that mocking is responsible for so much confusion in folks that ultimately don't really know what they're trying to accomplish.
The test suite I do run is the one where I hit "run" in my IDE and then it lets be jump to what failed and debug seconds later.
That, IMO, is what mocking is about: making the tests fast and easy to run so they actually are.
We need to mock, for example, DownstreamAPI01 that our app POSTS to and GETs from, so we can put in accurate and realistic faked responses.
We don't strictly need to mock other bits of our own apps - but many people choose to do so.
Martin Fowler writes very well about this, and I hadn't even appreciated the "split" between sociable unit tests and solitary tests:
https://martinfowler.com/bliki/UnitTest.html
I can see the appeal of both sides, but (a) I prefer sociable tests as they tend to test real code more than artificial constructs, and (b) people tend to get into a mess when they try to produce pure solitary tests, leading to the "not really testing your app" example in my earlier comment.
Most unit tests should be handled at the integration level.
In 2018 I could understand the aversion to them given how unbearably slow and flaky they could be but those problems are draining away with improved tooling and faster machines.
I think before long they'll be the default - a test that is 4 seconds slower and 30% more realistic will seem like a no brainer.
Unit tests should validate error paths are properly exercised and that we handle expected results appropriately. It is wild how often this is not expressed in unit tests.
TDD usually works well, and helps guide you towards designing a usable, testable API. Don't test getters and setters, test the actual logic you care about.
1. You still need to update the fake object once you gonna add a new behaviour.
2. Fake object has a tendency to become logic heavy. Someone will add a stupid-not-needed-map to test some shit you don't need to test in this unit\layer.
3. You only need to mock the behaviour you depend on. If you've added new Method and you need to mock it despite the fact you're not using it in the code you're testing - its a bit weird and smelly. Consider rethink SOLID principles at least.
4. Go. Gen. Effectively you can automate mock generation. Debatable but it's cool.
Just. Keep. Things. Simple. Depend on what you need. Mock behaviour you need. Try to test flattened structure - do not test the layer below the one you're testing ATM (unit tests).
All that said, I personally prefer the upfront investment in stubs. At scale, it is something readily reusable by other test suites and teams out there.
Use and depend on abstractions maybe?
I once knew a mockist who mocked out all the external code's behavior, but didn't add tests to ensure that the external code behaved as they expected.
That didn't end well either.
Just don't use mocks, unless they're the simplest thing that could work (usually not).
Whole hashtable? Or what?
You mock interface/api of “something” you depend on to model the behavior you might deal with within the part of code you test.
thats it. nothing else.
during unit test phase I want to make sure that this exact code works as a state machine given all the possible inputs/outputs and that it handles any possible situation any dependency can cause.
If you need to debug code BECAUSE of the tests and not WITH the tests then something is wrong with the architecture or approach.
tldr. mock dependencies. model behavior of the dependencies. make sure you can debug the code with the help of your unit tests on the very layer you’re working with at the moment.
While mocks can be useful, IMO "never use mocks" isn't a terrible rule of thumb.
Personally I've found it better to split pure business logic and side-effecting logic as close to the edge (API endpoint handler) as possible. Then it is trivial to see where side-effects happen and you get to easily write unit tests for the business logic that matter.
Mocking is mostly always a pain and I would rather not mock at all and do proper end-to-end for integration code if possible.
You can your db sum columns faster than you can grab the data, parse it, and compute the sum. In query calculations to avoid race conditions from doing the math on separate servers, etc.
Triggers and procedures are a thing.
The nosql/kv store hype missed a lot of stuff relational/sql dbs did well. Mostly because at the time they declared sql was too hard, or just never studied anything.
Sometimes the business logic is in DBs with stored procs.
Everyone is trying to figure out how to do it "right"(measuring by their needs)
and all of them struggle to realize that it is always context dependent - two different products, teams, companies may have different expectations and needs
I dont know how this happens that out of all arguable things in software engineering - is it that tests are the most chaotic ones, when up to the principle they are simple: if your code doesnt match specification, then scream!
Dont even get me on how TDD saves the world and is the only way how all software should be written (it is especially funny that tdd is accidentally successful by forcing api design first, yet ppl always argue for it due to red green transition and never due to api design first)
Also the concept of unit as in unit test may differ by kind of software
E.g unit test for parser may feel like e2e test for somebody who works in web apps
I've found some solace in finding one person that's really experienced in writing tests for my specific platform, and kind of following their blog as a "bible" of sorts. cough https://kentcdodds.com/blog cough
This way if a mocked test fails you find out much faster.
Databases, external APIs, dependency version updates, etc are among the most likely places where unintended changes get introduced. Why would you not test that when catching unintended breaking changes is one of the primary purposes of tests? Your simple business logic layer function converting one object into another is not going to be the problem, the database is
If you test behaviors, either with mocks or with fakes, you're not tied to implementation details. I can only assume this stops things from being what the author calls "brittle."
If your test doesn't test a behavior, then you are testing (at best!) an implementation detail. So maybe remove the test. If you're looking at code coverage, you should be able to get complete coverage testing at this level without testing implementation details or . . . you've found dead code - remove that, too! Tell your boss what a wise greybeard you must be because your commits remove bad things.
Whether or not you test behaviors, there are more running processes with fakes. There's a lot more happening that can fail. I'd call that brittle. Fakes have their place, but the reason to use them is because they're much less brittle (prone to fail) than full-stack end-to-end tests are. You can spin up your new microservice with all of its shared-nothing data stores, surround it with fakes, and completely exercise all of its reachable code - code like those error paths that are hard to hit otherwise. Better to test as much as you can in isolation first - faster feedback, easier to diagnose failures, and less test maintenance than a deployed environment.
1) Call the right method on the interface with the right data, and
2) Correctly handle different return values from the RPC.
If you change your code to call a different RPC method your test _should_ fail, I'd argue that's exactly the point of this sort of test.
On the other hand, for testing code that you entirely control, I 100% agree that a fake would provide a better test,
Everything else can be controlled and tested via DI.
You are doing it wrong if you have:
MyMock.Mocks(myMultiplyFunc).WithArgs(3, 4).ToCall(myAddFunc).Times(3).WithArg(4).ExpectsResponse(12).
Your multiply func being backed by add is not important to the unit test for multiplying. If you change it to have special handling for bitshifting in different cases, your mock tests break. Badly.
Mocks add coupling. Coupling is brittle. There are other testing options available. Are mocks sometimes useful? Maybe. I've not needed a mock library in 10 years of Go development designing and building robustly tested distributed sytems at scale. Every time I've seen a mock used it is gross. You end up with SomeSAASMock that is auto-generated to provide all the special handling of the real calls to the SAAS.
Don't mock out the full SendGrid API. Make a dependency interface "SendEmail(userID int, content []byte) (SendGridResp, error)". Inject that. Now you just have MyFakeSendGrid structs in tests and you can have it return an error when you want, or not. It is really that simple.
Depending on how detailed your integration or acceptance tests are, you can have sinks that capture network calls and return stubbed data or call the actual service.
Am I an idiot for thinking of this as a Mock? This is how I mock things anyway
Traditionally, a mock asserts against internal behavior while a fake does not.
The two scale differently. Mocks are per test, fakes are once.
If you are a small or medium project that doesn't make many RPCs just test with the real thing.
Once I got to google which makes many RPCs in every layer it just wasn't viable to not use mocking. I use mocks in almost every thing.
Yes, it makes unit tests brittle and couples to implementation. But unit tests are cheap and often need to be changed anyways. It also allows you to have control over certain situations like what happens if an error occurs. Which can be hard to test for in integration tests.
In short I feel like avoid mocks if you can but embrace them if you can't.
If anything mocking is useful for verifying that something is called whether that be a service or a repository method.
Equally mocking things like mappers is overkill, a nice balance is to inject some mocks and some "real" instances of dependencies into the class that is under test.
The added benefit is that you are testing the dependencies (if the mapper for example) are working.
Either way, RR is good to have in the toolbox.
I've actually messed around with a library that makes stubs by recording external calls to things like APIs or databases. Sure, it can get a little brittle at times, but it's kind of cool because it's using real calls to create these stubs, and you don't have to put in a ton of extra work. Like anything else, it's about weighing the pros and cons and seeing what fits best for your project.