{
  "$type": "site.standard.document",
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreidrewo5ray46kcoakgn2h3qbl253epfshppnzlvthetwdszw3fc6e"
    },
    "mimeType": "image/png",
    "size": 124142
  },
  "description": "Different approaches to testing APIs in Ruby with RSpec. Code and opinions on which works best.",
  "path": "/writing/testing-apis-in-ruby-with-rspec/",
  "publishedAt": "2018-08-25T22:00:00.000Z",
  "site": "at://did:plc:b7i4j3fb3ltoyvorclobat7j/site.standard.publication/self",
  "tags": [
    "Ruby",
    "Testing",
    "RSpec"
  ],
  "textContent": "Testing API calls is a little bit hairy. We can assume that the API will return the results specified in their documentation, but we'd still like to see what our API client returns; this may differ considerably from what the API itself gives us. The last thing we want to do is hit the actual endpoints: this is slow, may incur charges or rate limiting, the server may be down at test time, endpoints may change, and it's just not cool to hammer an external service. This is a little round-up of approaches I've had success with. WebMock WebMock is a the gem for stubbing HTTP endpoints, which lets us assert that our API client sends a correct request to a server. We can also specify what the request will return and test our API client that way. Here's a simple example of how this might work: There's nothing wrong with this, but it quickly becomes unwieldy as the response grows in size. This can be mitigated by using fixtures rather than specifying the JSON inline. Just dump actual API responses into JSON files and read them: For most cases, this will suffice. In some cases, though, you may want to run your fake server alongside the app in development, which isn't possible with all of the stubbing logic in the specs. VCR VCR is a really cool approach. Instead of defining API responses yourself, VCR will let them go through the first time and record the response. The next time your spec runs, VCR will simply play back the previous response. The result is really clean tests, with no manual stubbing required: VCR is really nifty and it may work for you, but I'm not really a fan of this approach. In cases where the API changes drastically, we'll need to clear all existing \"cassettes\", rerun specs, and pray that the endpoints still exist. What if post 1 was deleted? You'll need to retrieve a different post and fix all relevant cassettes. I've cursed VCR's approach at least once for each project I've worked on that has adopted it. Anyway, as far as I'm aware, we still can't use our VCR cassettes alongside the app in development. Fake Server A third approach involves building your own little fake server to return expected responses. This allows us to sidestep any need to stub things and allows us to run our app against it in development if necessary. I've previously built these with plain Rack, but the boilerplate routing logic can become pretty messy. Instead, I'd like to suggest a little gem named Cuba. Cuba gives us a simple DSL to define routes and responses, which is really all we need. It also doesn't pull in a bunch of unnecessary dependencies. Building the client Let's build a simple example. Below is our our ApiPost class. I've left out any error checking that you'd want in production-grade code. .get retrieves the post from the endpoint and returns an ApiPost object. Note that we first try to retrieve the endpoint from an environment variable, APIENDPOINT, and use DEFAULTENDPOINT if it is not found. Grabbing a fixture OK, let's define a post fixture. Normally I'd go to grab an actual API response at this point, but since we don't have one, here's a JSON file, dumped in my test app at fixtures/posts/1. Building the fake server Next, let's build the fake server with Cuba. I recommend taking a look at the documentation to better understand Cuba's API when necessary, but it's pretty straightforward. I've placed this in servers/fakeapiserver.rb. We define a little helper to read a fixture file and define a route which returns the content of our posts/:id fixture. The :id portion of the route will be replaced with whatever we actually request, just like in Rails. The last line is interesting, though. This is what lets us run the server simply by executing ruby servers/fakeapiserver.rb in the command line. Let's check if everything is working as expected. Great! We now have a working fake server. Assuming we have a Rails app that we want to run and hook up to the fake server, we can do this: APIENDPOINT=http://localhost:8080 bundle exec rails server. The RSpec helper Let's define a helper method to set up our server in the testing environment. This would usually go in some file under spec/support/helpers/, but for the sake of demonstration I'm just shoving it directly into spec/spechelper.rb. The comments should explain most of the weird stuff going on here. In the WEBrick.run call, we disable all logging to give us a cleaner test output; you might want to comment these lines out when debugging a failing test. The main point of interest here is in the StartCallback. We pass this a lambda which is called as soon as the server starts up. If we don't explicitly wait until the server is started up, execution of the current thread (our specs) will just carry on, and probably finish before the server ever starts up. I haven't found a better way to deal with this than simply polling a variable, but it works just fine. For the sake of speed, I would recommend running this in an around block rather than in each individual spec. Testing Time to write a little test. That's all there is to it No stubbing needed, and we have a server which can run in development when necessary. You can find the example project using this code on GitHub. Summary There are tons of ways to test external services in Ruby, but these are a few that worked for me. These approaches should be replicable regardless of language or framework.",
  "title": "Testing APIs in Ruby with RSpec"
}