I'm new to mocking (to serious unit testing, really), is mocking functions of 3rd-party libraries and/or web API's recommended? It sounds like a good idea, especially for functions that need to hit the network for a web API, but maybe I'm missing something?
I would say it's generally a bad idea to mock 3rd party libraries. It's code that's not under your control and testing with a mock which may not match the actual behaviour could cause you trouble. In the case of network requests, something like VCR[1] helps a lot.
I would disagree. If you want to unit-test your code that depends on those 3rd party libraries, then your choices are to:
1) Use the real thing. This can be slower and a lot less reliable.
2) Capture some typical output of the 3rd party api, and make a mock that emits it.
Which technique is useful? Both are. But the second one gives you fast-running fine-grained repeatable unit tests so therefore is your first line of testing.
So you discover in an integration test (#1) that the live api has a behaviour that the mock doesn't, maybe an occasional malformed response due to an error at the other end. Great, capture it and add it to the mock. Now you can reproduce it at will and write fast tests on how you handle it.
3) Define a Gateway interface (e.g. DataStoreGateway) that presents exactly the API you want to consume; write one implementation of it that speaks to your third-party API (e.g. ODBCDataStoreGateway), and another that doesn't do much (e.g. MemoryDataStoreGateway).
Note that a trivial gateway-implementation, like MemoryDataStoreGateway, isn't a mock! It's a fully-functional component that works perfectly well from its consumers' perspectives. But, unlike the version that consumes a third-party component, it doesn't have nearly any failure modes that would confuse your tests. It just does what it does, simply, and lets your tests test what you're trying to test.
And note that when you stop mocking, and limit yourself to switching out fully-fledged gateway implementations, "dependency injection" stops being this huge pain with Factories and heavily-parameterized initializers. Instead, you can just make your objects discover their collaborators through a service registry. Your unit tests just register the trivial implementations in the service registry on setup().
---
† Don't get a bad taste in your mouth thinking about Spring here. A simpler, healthier example is Erlang's "global" module (http://erlang.org/doc/man/global.html).
> But, unlike the version that consumes a third-party component, it doesn't have nearly any failure modes that would confuse your tests.
That's all fine until you need to test handling the third-party component's failure modes.
> Note that a trivial gateway-implementation, like MemoryDataStoreGateway, isn't a mock!
Probably not. Though it depends at which point you insert the "trivial" components. If you're using a trivial component to unit test a collaborating class that isn't at the edges of the system, then that's a mock and it's helpful. It's not the only helpful technique or even the usual one, but "mocks are never helpful, always avoid them" is silly thing to say.
A mock store for one unit test is far more lightweight than a MemoryDataStoreGateway, and allows an easy way to simulate server errors, so sometimes it is a better thing to code.
> This is what "dependency injection" is really about, actually: not inserting mock-objects ... but rather allowing you to plug in ... versions of your collaborators.
It can be about both. They are not exclusive.
> Instead, you can just make your objects discover their collaborators through a service registry
You may be happy with every object reaching out to a global singleton. But I'm going to stay well away. Thanks but no thanks.
"Global" is a matter of perspective. You can pass your objects a service registry to initialize themselves from, if you like. You can have as many service registries as you like.
The difference, then, from passing them their pre-initialized collaborators directly, is that if they need to create new collaborators on their own, they can ask the service registry what the current implementation they should be instantiating is. (It's basically a change in perspective from having objects that spawn specialized, short-lived objects, to having long-lived objects that can deal with changing conditions around them. Think about, say, Docker containers using links vs. etcd discovery. It's nice to not have to bring down your webserver just because your DB moved to a new IP address.)
Also, if you were thinking about my Erlang example specifically when you said "global singleton object" -- Erlang, despite the name, doesn't have one global registry, either. Instead, Erlang's process registry is per-node -- and nodes are the boundary where you deploy a release. In fact, ignoring the pragmatic OS-threading and networking implications, an Erlang node is almost exactly a "switchboard" on which services are plugged into one another, and an Erlang release is almost exactly a declaration of the current connections on such a switchboard. Need a place where your service is registered to X instead of Y? Bring up a node where that's true.)
When you're testing the third-party component's failure modes, the tests you're writing are functional tests belonging to the nontrivial gateway implementation (e.g. ODBCDataStoreGateway).
The important thing about the gateway interface is that it's an error boundary: you don't need to think about "server errors" when dealing with a DataStoreGateway; DataStoreGateways don't have server errors. The ODBC object the ODBCDataStoreGateway holds might hand it a server error, but it won't propagate that back to you. It'll just put out a plain† DataStoreGateway::InsertError or somesuch.
† The exception should probably hold a copy of the implementation-exception that triggered it, but this is just for the sake of debugging the implementation. The client should never unwrap the internal exception.
> you don't need to think about "server errors" when dealing with a DataStoreGateway; DataStoreGateways don't have server errors.
I'm not sure why you even make that distinction. More than once I've dealt with servers that occasionally time out and fail; (say once a week or so). I wish to test what I show on my UI under those conditions, and what part of the text and status codes returned from the remote server that represent (or whatever) its failure I will choose to display.
Doing so without using a mock seems pointlessly obtuse and roundabout when I can make a mock to insert whatever error response I want at any interesting point in my code.
2. Your client has a network connectivity problem.
I've never seen a client who needs to know #1. All they care about is that there was a DataStoreGateway::TransientInsertError, and that TransientInsertErrors mean retrying and notifying the UI that their work hasn't been committed yet.
#2 isn't a problem the DataStoreGateway should be responsible for, because it will affect almost everything in the system in one way or another, and you don't want to have to write that code repeatedly. It's more likely a kind of Alarm--the kind of thing that gets pushed out from wherever it is to a global alarm handler, which then does something like putting the UI into a different root view-controller state, e.g. darkening it and covering it with a modal saying "lost connection."
I was thinking more of the case where the 3rd party library does no network communication. If it still takes a long time to execute, that may be a concern. But if the output is complex, then mocking it may be more trouble than it's worth.
For network requests to an API, your option #2 is exactly what I was suggesting.
There's a lot of value in coarse-grained mocks like this for testing the system; I also find value in finer grained mocks for testing smaller components in isolation. But neither of these techniques is the only valid one.
And neither of them forces you to write things upfront that you don't need later; which is what the original article seems to imply.
VCR seems similar to $httpBackend. The main difference is that it automatically records the HTTP response from the target server the first time tests are executed. Then it allows you to reuse that response in future test runs to save having to manually mock responses.
Anyway, sounds like we're more or less agreeing :)
For something like a web API I think most people would support mocking. If it's just a library it's a lot less clear-cut; my approach would be to use the real thing until it gets to be a problem (because it's making your tests slow, or because it doesn't expose the right interface, or because it's too hard to get it into the state that you want to test). Certainly use mocks when you need to, but the rest of the time there are arguments for both sides and you probably need to figure out what you prefer yourself.