Skip to Content
Course content

105: VCR for Mocking HTTP Requests

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a project the other day that relied heavily on the GitHub API. I had a small service class designed to fetch a user's public profile and check if they had a specific badge. At first, I just wrote a test that hit the actual API. It worked, but then I noticed something annoying: every time I ran my test suite, there was a noticeable 0.5-second lag. Then, after running the tests a few dozen times during a refactor, GitHub hit me with a rate limit error. Suddenly, my tests were failing not because my code was broken, but because GitHub didn't like how often I was calling them.

The Sluggish Reality of Live Requests

Let's look at the code I started with. I have a simple GithubClient that uses Net::HTTP. Here is the test I wrote to make sure it parses the bio field correctly:


require 'minitest/autorun'
require 'net/http'
require 'json'

class GithubClient
  def self.fetch_user(username)
    uri = URI("https://api.github.com/users/#{username}")
    response = Net::HTTP.get(uri)
    JSON.parse(response)
  end
end

class GithubClientTest < Minitest::Test
  def test_fetch_user_returns_bio
    user = GithubClient.fetch_user('ruby-lang')
    assert_equal "The Ruby language", user['bio']
  end
end

If you run this, it's "correct" in a vacuum, but it's a nightmare for a CI/CD pipeline. It's slow, it requires an internet connection, and as I found out, it's subject to rate limits. I initially tried using WebMock to manually stub the request, but do you know how tedious it is to copy-paste a 50-line JSON response into a Ruby string just to mock one API call? I spent more time formatting strings than writing logic.

Automating the Mock with VCR

This is where VCR comes in. Instead of me manually typing out what the API response should look like, VCR records the real response the first time the test runs and "plays it back" every time after that. It's like recording a snippet of the internet and saving it to a file.

First, I'll add vcr and webmock to my Gemfile. VCR actually uses WebMock under the hood to intercept the requests, so we need both. Then, I have to tell VCR where to save these recordings (which it calls "cassettes").


require 'vcr'
require 'webmock'

VCR.configure do |config|
  config.cassette_library_dir = "spec/vcr_cassettes"
  config.hook_into :webmock
end

Now, I just need to wrap my test logic in a VCR.use_cassette block. I'll give the cassette a name, like 'github_user_ruby_lang'.


def test_fetch_user_returns_bio
  VCR.use_cassette('github_user_ruby_lang') do
    user = GithubClient.fetch_user('ruby-lang')
    assert_equal "The Ruby language", user['bio']
  end
end

Peeking Inside the Cassette

When I run this test for the first time, VCR notices there is no file named github_user_ruby_lang.yml in my cassettes directory. It lets the request go through to the real GitHub API, catches the response, and writes it to disk. If you look in spec/vcr_cassettes/github_user_ruby_lang.yml, you'll see something like this:


http_interactions:
- request:
    method: get
    uri: https://api.github.com/users/ruby-lang
  response:
    status:
      code: 200
      message: OK
    body:
      encoding: UTF-8
      string: '{"login":"ruby-lang","bio":"The Ruby language", ...}'

It's just YAML. It's human-readable and version-controllable. I can actually commit this file to Git, meaning my teammates don't even need to hit the API to run the tests; they'll use my recorded response.

The Instant Payoff

Here is the magic part: run the test again. You'll notice it finishes almost instantaneously. VCR sees the cassette exists, intercepts the outgoing request, and injects the YAML body directly into the response object. No network trip, no rate limits, and no more waiting for the API to wake up.

One thing to keep in mind: if the API changes, your tests will still pass because they're using the old recording. Every now and then, I delete my cassettes folder and run the suite again to make sure the real-world API hasn't drifted from my mocks. It's a much cleaner way to handle integration testing without the fragility of a live connection.




📋 Practical Task

Exercise: Recording Pokemon Ability Data

You are building a tool that analyzes Pokemon abilities using the PokeAPI (https://pokeapi.co/api/v2/pokemon/{name}). Your goal is to ensure your code can correctly extract the first ability of a Pokemon without making real network requests every time you run your tests.

Requirements:

  • Create a class PokemonClient with a method fetch_first_ability(name) that returns the name of the first ability listed in the API response.
  • Set up VCR and WebMock in your test file.
  • Configure VCR to store cassettes in a directory named fixtures/vcr_cassettes.
  • Write a test for fetch_first_ability('pikachu') wrapped in a VCR cassette named pikachu_ability.
  • Verify that the test passes and that a pikachu_ability.yml file is created in the specified directory.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.