Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
105: VCR for Mocking HTTP Requests
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
PokemonClientwith a methodfetch_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 namedpikachu_ability. - Verify that the test passes and that a
pikachu_ability.ymlfile is created in the specified directory.
There are no comments for now.