A while ago I contributed a change to the well-known Ruby gem VCR. You can read all about it in this pull request, but basically it allows you to prevent interactions from being recorded until your test passes.
To be able to make this change, I obviously had to dive into (a portion of) the source code to understand what I had to change. By reading the code I discovered some VCR features I did not know, so they might be new to you too. Here are six things you might not know about VCR.
1. You can change the serialization
VCR is well known for the YAML files it uses to persist cassettes. By default that isn't a bad format: it is readable and Ruby developers are generally familiar with it. But if you would want to change the serialization, you can set it in the configuration. That could come in handy, for example if you're exporting the cassettes to a separate system that does not support YAML.
VCR.configure do |config|
config.default_cassette_options = {
serialize_with: :json
}
end
The available serializers are: :yaml
, :syck
or :psych
for YAML format, :json
for JSON format (powered by MultiJson) or :compressed
, which adds compression to the :yaml
serializer. You can even create and use your own serializer. It should respond to file_extension
, serialize
and deserialize
:
module VcrXmlSerializer
def file_extension
'xml'
end
def serialize(hash)
Qyoku.xml(hash, key_converter: :none)
end
def serialize(xml)
Nori.new(parser: :nokogiri).parse(xml)
end
end
VCR.cassette_serializers[:xml] = VcrXmlSerializer
VCR.configure do |config|
config.default_cassette_options = {
serialize_with: :xml
}
end
2. You can change the persistence
By default VCR generates files, which works fine for most use cases. You can implement your own storage though, for example to write all interactions to a database:
module VcrDatabasePersister
def []=(file_name, content)
db.execute("INSERT INTO http_interactions (name, content) VALUES (?, ?)", [name, content])
end
def [](file_name)
db
.get_first_row("SELECT content FROM http_interactions WHERE name = ?", [name])
.fetch('content')
end
private
def db
SQLite3::Database.new("test.db")
end
end
VCR.cassette_persisters[:database] = VcrDatabasePersister
VCR.configure do |config|
config.default_cassette_options = {
persist_with: :database
}
end
3. You can hook into events
VCR supports a number of hooks that allows you to change data before it is used or persisted by VCR:
-
before_record
: change the HTTP interaction before it is recorded -
before_playback
: change a cassette before it is registered for use -
before_http_request
,after_http_request
andaround_http_request
to perform an action before and/or after a HTTP request is performed (either stubbed or real requests).
For example, you can wrap a timeout around each request:
VCR.configure do |config|
config.around_http_request do |request|
Timeout::timeout(500, &request)
end
end
See: https://relishapp.com/vcr/vcr/v/5-0-0/docs/hooks
4. You can use placeholders
You can use the config option define_cassette_placeholder
, aliased as filter_sensitive_data
to replace data before it is written to a file. You can, for example, use it to filter passwords:
VCR.configure do |config|
config.filter_sensitive_data('<MASKED>') { ENV['API_PASSWORD'] }
end
See: https://relishapp.com/vcr/vcr/v/5-0-0/docs/configuration/filter-sensitive-data
5. You can use cassette data inside your test
One use case is when a HTTP request is time sensitive (eg. when signing a request). When using use_cassette
the cassette object is passed to the block, allowing to grab the recording time:
VCR.use_cassette('example') do |cassette|
request = build_api_request
timestamp = cassette.originally_recorded_at || Time.now
request.sign_with('some secret key', timestamp)
end
See: https://relishapp.com/vcr/vcr/v/5-0-0/docs/cassettes/freezing-time
Besides the recording time, the cassette exposes more metadata like the name, configuration and the file path. For more on the public API of Cassette
see: https://rubydoc.info/gems/vcr/VCR/Cassette.
6. You can use ERB in cassettes
Yes, you can use ERB in a cassette. There are probably not many cases where you should do it, but here is an example just to show what is possible. We use ERB to inject an API key in a cassette:
---
http_interactions:
- request:
method: get
uri: http://example.com/time?api_key=<%= api_key %>
body:
encoding: UTF-8
string: ''
headers: {}
response:
status:
code: 200
message: OK
headers:
Content-Type:
- application/json;charset=UTF-8
Content-Length:
- '47'
body:
encoding: UTF-8
string: "{\"time\":\"2019-08-08T20:08:25.604188+02:00\"}"
http_version: '1.1'
recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
recorded_with: VCR 2.0.0
VCR.use_cassette('time', erb: { api_key: ENV['TIME_API_KEY'] }) do
response = Net::HTTP.get_response('example.com', "/time?api_key=#{ENV['TIME_API_KEY']}")
puts "Response: #{response.body}"
end
See: https://relishapp.com/vcr/vcr/v/5-0-0/docs/cassettes/dynamic-erb-cassettes
That's all, folks!
Hopefully these tips were useful to make testing your application easier. Do you need more help? At Kabisa we know a lot about writing good tests. Leave us a message if you would like to get in touch.
Top comments (0)