I've been going round in circles trying to figure out the best way to unit test an API client library I'm developing. The library has a Client
class which basically has a 1:1 mapping with the API, and an additional Wrapper
class which provides a more user-friendly interface over the top of the Client
.
Wrapper --> Client --> External API
I first wrote a bunch of tests against both Client
and Wrapper
, effectively just testing that that they forward to the appropriate functions of whatever the operate on (Wrapper
operates on Client
, and Client
operates on a HTTP connection). I started feeling uncomfortable with this, however, because I feel like I'm testing the implementation of these classes, rather than the interface. In theory, I could change the classes to have another perfectly valid implementation, but my tests would fail because the functions I expected to be called aren't being called. That sounds like fragile tests to me.
After this, I thought about the interface of the classes. The tests should verify that the classes actually do the job they're meant to do, rather than how they do it. So how can I do this? The first thing that comes to mind is stubbing the external API requests. However, I'm nervous about oversimplifying the external service. A lot of the examples of stubbed APIs I've seen just give canned responses, which sounds like a really easy way to only test that your code happens to run correctly against your fake API. The alternative is mocking the service, which is just infeasible, and would need to be kept up to date whenever the real service changes - that feels like overkill and waste of time.
Finally, I read this from another answer on the programmers SE:
The job of a remote API client is to issue certain calls - no more, no less. Therefore, its test should verify that it issues those calls - no more, no less.
And now I'm more or less convinced - when testing Client
, all I need to test is that it makes the correct requests to the API (Of course, there's always the possibility that the API will change but my tests continue to pass - but that's where integration tests would come in useful). Since Client
is just a 1:1 mapping with the API, my concern before about changing from one valid implementation to another doesn't really apply - there's only one valid implementation for each method of Client
.
However, I'm still stuck with the Wrapper
class. I see the following options:
I stub out the
Client
class and just test that the appropriate methods are called. In this way, I'm doing the same as above but treating theClient
as a stand-in for the API. This puts me right back where I started. Once again, this gives me the uncomfortable feeling of testing implementation, not interface. TheWrapper
could very well be implemented using a completely different client.I create a mock
Client
. Now I have to decide how far to go with mocking it - creating a complete mock of the service would take a lot of effort (more work than has gone into the library itself). The API itself is simple, but the service is quite complex (it's essentially a datastore with operations on that data). And again, I will have to keep my mock in sync with the realClient
.I just test that the appropriate HTTP requests are being made. This means that
Wrapper
will be calling through a realClient
object to make those HTTP requests, so I'm not actually testing it in isolation. This makes it a bit of a terrible unit test.
So I'm not particularly happy with any of these solutions. What would you do? Is there a right way to go about this?