Testing Remote Web Services The Quick And Nasty Way
In my Beyond Frameworks talk, I explained how a component-based architecture can help answer some of the important (i.e. expensive!) questions you might face when creating long-lived apps that rely on a PHP framework. In this series of blog posts, I'm going to look at how to go about creating and working with components.
To be a success, all components need high-quality unit tests, and this applies to the component I am building, a client for Repustate.com's semantic analysis API. But how do you go about creating the unit tests for a remote web service without the process becoming onerous or even dangerous?
Can You Test The Quick And Nasty Way?
This is the first question that always comes to mind whenever I'm writing an API client. I don't want to do it the classical way, of creating my own mockup of the web service, if that can be avoided. Mockups are slower to build, and they perpetuate any misunderstandings I have about the API and its behaviour. I want to test directly against the API, so that I can catch errors immediately, and have confidence that my code will work in production. Technically, these are integration tests not unit tests, and there are good arguments out there for also having the mocked up unit tests too, but I'm a pragmatic engineer, and I need to get this done before my app's requirements change.
Now, this all comes down to whether or not the web service makes life easy for developers or not.
- Does the web service provide a test API, where I can reset the state of my account to a known position before I start testing?
This is the holy grail for writing tests against an API. Tests must be deterministic - same data in must always produce the same result out - to be trustworthy. Being able to use a test API, where I can control the starting conditions before I run my tests, gives me everything I need to safely test against the web service's endpoint.
Sadly, you're more likely to see a unicorn than come across one of these, but never give up hope!
- Is the API destructive in a disruptive way?
- Do I have to mock up the API locally?
This is the testing of last resort, tbh a€¦ but sometimes you're left with no choice on the matter.
And, it's worth me mentioning that API mockups are actually the best way to deal with regression tests against your own client, as they allow you to catch badly-formed requests to the remote API perfectly.
If I have to test against the live API, can I do so safely? Can I write tests that will not cause trouble for my production systems that are using the API?
Often, the answer to this is a€˜yes', which is great news. Not as good as our unicorn-test-API above, but something I can work with.
At the time of writing, I couldn't find my unicorn-test-API for Repustate, but because their live API is entirely non-destructive, there is little risk in making my unit tests for this component work against the live API.
Never Commit Your API Key
Like many web services, Repustate's requires you to apply for an API key, which is then used to authorise your web service requests. This allows Repustate to keep track of who is doing what, and allows them to revoke access if the web service is abused.
To run the tests against the live API, my test code is going to need to know the API key that I've obtained from Repustate. But if I commit that to the component's public GitHub repo, I'm simply asking for every script kiddie to abuse that key. It's simply not safe to commit this key. And it wouldn't be safe if the repo was private; how do I know that one day the repo won't be open-sourced, with the key available to find in the repo's history?
Never, ever, ever, commit an API key to a component's source control repo.
But my test code needs this API key. Where can it get the API key from? We could put the key in a config file that's installed by an uber-secret component, and indeed this is a practical approach when you're controlling the deployment of apps in the enterprise. (Just don't make it so secret that your
Truncated by Planet PHP, read more at the original (another 1706 bytes)