I’ve been working on an open source R package wrapping the dev.to API. After a bit of prototyping the core feature and then some iterative development to make it a bit more useful to a user, it’s starting to settle into something that is worth testing\*.
Sometimes people seem a little surprised that this is something R users/ Data Scientists even think about, but R is a programming language, just like all the others, except different, just like all the others. Testing in modern tidyverse R is usually accomplished by the
testthat package. If you are more familiar with testing in other languages, this quote from the Overview might help:
testthat draws inspiration from the xUnit family of testing packages, as well as from many of the innovative ruby testing libraries, like rspec, testy, bacon and cucumber.
In R we have a rich ecosystem of developer tools to help us make packages.
testthat is one. Another one is
usethis. See what they did there? Anyway, for the purpose of this article, you just need to know that a package called
usethis helps us set up things for us to make development easier. In the future I might write something more about it ¯_(ツ)_/¯.
Assuming that you have a well formatted R Package structure, you can easily enable a testing framework and all the boiler plate you might need with one line:
Which will do a few things for you and tell you what it’s doing.
From there, we can use
usethis::use_test(). If you have a file open in RStudio which is an
*.R file containing function definitions it will then make a new test file for you:
The contents of that test file will be silly boilerplate, so now it’s time to do some real work.
This is the boilerplate. If you run it, you might be surprised that nothing happens! Well, actually a lot of stuff happens, but it doesn’t really tell you about it by design. If you make a test that isn’t going to pass however…
## Error: Test failed: 'maths works'
## * <text>:2: 2 * 2 not equal to 5.
## 1/1 mismatches
##  4 - 5 == -1
testthat. Write a
test_that() function, which has a name, and then a block of expectations to check against.
So this works fine for traditional unit tests, where we can give discrete calculations, or check that a given function gives a specific output end to end, where we control both the test, but also the function as a whole. But what if we’re reliant on some ‘external’ process that we might not control. The dev.to API for instance? I’m not employed by dev.to (through I am looking for a new opportunity), so I don’t get to play around inside the API system, but I do need to prove that any code I write will behave the way I want it to based on their API requests and responses. A simple way to prove this is to mock their service (i.e. impersonate it, not tell it it’s silly). This is where
webmockr comes in.
webmockr is an:
“R library for stubbing and setting expectations on HTTP requests”
Perfect. Let’s write something using both
webmockr we make stubs. These are fake, minimal objects that are similar to test fixtures. We know their properties, because we made them, and we want to make sure that any functions we write interact with these objects in a predictable way. Another way of looking at them is as a fake API. They look like an API, with responses and status codes, but they only exist in our test suite. This means I don’t need to bombard dev.to with requests any more to make sure I haven’t broken anything.
my_user <- dev.to.ol::
This code first of all sets up our test file to understand that requests will be sent as if from the
httr package. It then clears the registry, just to make sure nothing is left in a cache. It then populates the now empty cache with a new stub. This stub will respond to a
GET request to the URL, and will return a simple text body, and a 200 status code. The function I want to test is then run, which in this environment hits the stub, not the real api. The object that is returned is then checked by
test_that to make sure it is a
response type object, and that is has a status code that has the value 200.
This proves a few, specific things. That the function returns a
response that has a 200 status code if it trys to
GET from that specific URL. However, APIs actually return quite a lot of information by default and maybe I care about more things than a 200 status code. They can also have quite complex structures, so using this method to make a fake response could get very awkward if I am trying to make a realistic response. Also, what if the structure of the api changes underneath us? It is in beta after all. A big change would mean all those carefully written pipes would have to be rewritten by me every time. Blergh. Luckily we have a solution for that too!
vcr does not stand for
Very Cool Responses, but I think it should. From it’s own description:
Record HTTP calls and replay them
It’s an R port of a ruby gem of the same name, this package allows you to ‘record’ the response of an API to a YAML file ‘cassette’. You can then ‘play’ the ‘cassette’ back during the test as if the API was being actually called. If you’re still not sure where the name comes from, then you might be a little to young to get the reference.
vcr has a few things it needs in a project to run, and though it doesn’t have its own entry in
usethis, it does have it’s own set-up function in a similar style:
From there, we can use the normal
testthat flow. Here’s an example using the
POST to write a new article.
Well, that’s an easy change! The only difference from the first test is that we have wrapped the function we are testing in a
use_cassette block, inside the
test_that block. Now, the first time this function is run, you get this. A huge YAML file that describes the response of the actual API. Now, every time the test is run, that cassette will get loaded as the ‘mock’, and it’s so much more developed than our stub! We can test against anything we want in the response, and even better, the response is totally human readable.
What about changes? What happens if you make a change to the data you use to test the function that invalidates the
cassette? What if the dev.to spec changes? Easy, all you do is delete the test and re-run. The function will then go and get a new response, and populate the file again. Your tests then run against the new file. You can even commit these to version control. Then you can tell exactly when an API change occurred, and what was different afterwards.
vcr packages are being maintained by @sckott, who is an active writer here on dev.to. and I think he’s really worth a follow. He also works on ROpenSci, which I think is also a really cool project. If you are working with R on a scientific/research project you should be extra interested.