Deterministic testing of remote APIs with VCR
If you decide to test your app properly, you come into situation when you need to make requests to external services (e.g. to test your adapters for remote APIs). After you start, you quickly realize two things:
- API does not necessary return same response and with the same order of elements - your tests cannot be deterministic. If you try to, you just “hope” that the response won’t change .
- If you run your tests and they fail, you can’t say whether it’s you who screwed things up or the API provider did something terrible and shameful.
However smart guys from Rails community came up with sweet gem called VCR and other smart people ported it to PHP, so it would be available for the bigger mass of developers. The gem serves as a recorder of your network requests and provides them later for your tests even when you’re offline.
Why the hell another library?
To write deterministic tests. And why would you needed deterministic tests? It’s simple. Imagine you implement adapter for API of your payment processor, which requires to insert date, generated token and limits your amount of requests per minute to 20. Now you want to write 50 unit tests, for each feature of your adapter. Your tokens get invalid within couple of minutes and your tests are broken - response has changed. And if you run your 21st test? Same situation again. API just banned you for minute because of your amount of requests.
So you need something else. You could override your classes with stub test classes returning same response over and over again. But that would require too much of work and altering the code. So what else? You could record it. Automatically if it’s possible. And it is.
Back to the roots
VCR technology itself is pretty old (younger may not even remember), but it serves as a great simplification model for our use case. As I mentioned before, VCR library for Ruby or PHP (I didn’t do the research if it exists for other languages, feel free to search) is right there for you. After you initialize it in the beginning of your tests and insert proper cassette (file containing recordings for your test), all network communication is intercepted.
The library compares request’s metadata (method, params, URI…) and if the request is already recorded in the cassette, it will return recorded response instead of making the real request. If it doesn’t find recorded response, it makes the real request and saves both request and response into the provided cassette, so you can use recorded version next time you run the tests. Recordings are by default stored in YAML format, as it is more readable than JSON. You are free to edit them as you need - as long as it stays valid. Neat, isn’t it?
As for ruby, it works for standard Net classes. As for PHP, it only works for CURL requests. If you make your requests via writing to and reading from streams, you will need to alter your code in order to make the library work properly.
In the introduction I mentioned two problems, which are easily solvable if you use recordings for your tests:
- Once you record your requests, every time you repeat it you get the same response over and over again. Proper deterministic tests.
- If your tests fail, it’s definitely your fault, because the response didn’t change. You still can write tests without using VCR to test live APIs - then you’ll have two sets of tests (VCR, live) each telling you who is responsible for their failure (you, them).
For Ruby version I recommend to read documentation on the official website or to watch this railscast (pro), which will tell you everything you need.
I haven’t found a good tutorial for PHP yet, but everything necessary is written on GitHub page. Please see, that installation requires adding curl runkit extension into your PHP and external libraries are fetched via Composer.
Your requests are stored in cassettes - files. The best way to divide your cassettes is per one test suite (e.g. one cassette per payment processor) or even per test, if the test is making extensive amount of requests.
If you work in a team, don’t forget to push your cassettes into the repository so the others can run the tests too. If you add new feature and you need to re-record the requests again, feel free to remove cassette for given test and record it again completely. Then don’t forget to commit the change.
All in all, you don’t need to use this library. Unless you want to write proper unit tests and have your code base test coverage as high as possible. VCR speeds up the tests, makes them deterministic and is able to run them when you’re offline. Once you install it and record the requests, you don’t need to handle anything else. You’ll love it, trust me.