Whisperer
Do you hate fixtures? I do as well. The purpose of this library is to make your life easier when your application works with external API and you use VCR to stub that API.
Features
- Describes VCR cassettes with Ruby.
- Describes entities for a response body of VCR cassettes with FactoryGirl.
- Posibility to inherit VCR cassettes (actually cassette builders describing VCR cassettes, but the effect is the same).
- Serializers to serialize a response body to a format supported by your API.
Installation
Requirments:
- Ruby 2.0.x or 2.1.x
Add this line to your application's Gemfile:
gem 'whisperer'
And then execute:
$ bundle
Or install it yourself as:
$ gem install whisperer
To create default directories' structure and the config file with default options, you need to execute:
$ rake whisperer:install
It will create cassette_builders
directory in your spec
folder and .whisperer.yml
file in your root directory of the project.
If you want to create the config file only, you need to execute:
$ rake whisperer:config:create
Usage
Describing VCR cassettes
VCR cassettes are described in cassette builders
. It is Ruby DSL which repeats structure of VCR cassette:
Whisperer.define(:arya_stark) do
request do
uri 'http://example.com/users/1'
method :get
end
response do
status do
code 200
'OK'
end
headers do
content_type 'application/json;charset=utf-8'
end
body do
encoding 'UTF-8'
factory 'arya_stark'
end
end
recorded_at 'Mon, 13 Jan 2014 21:01:47 GMT'
end
But, it is Ruby, hence, we can benefit from that. Whisperer uses FactoryGirl to describe a response body. If you are not familar with FactoryGirl, please, make sure, you know how to use it bofore going on. There are a few ways how factories can be used.
You can use one single factory:
body do
factory 'arya_stark' # we provide only name of the factory
end
arya_stark
factory is taken to generate the response body:
string: '{"first_name":"Arya","last_name":"Stark","group":"member"}'
You can use multiple factories to generate collection for your response:
body do
factories ['robb_stark', 'ned_stark'] # again we provide only names of factories
serializer :json_multiple
end
robb_stark
and ned_stark
are taken to generate the response body:
string: '[{"first_name":"Robb","last_name":"Stark","group":"member"},{"first_name":"Ned","last_name":"Stark","group":"member"}]'
You can pass factory objects instead of their names:
body do
factories = (1..20).to_a.map do |i|
factories << FactoryGirl.build(
:article,
id: 'testid' + i,
title: 'test name' + i,
body: 'desc' + i
)
end
raw_data factories
serializer :json_multiple
end
It is very useful, when you need to generate dynamically instances of a factory.
Inheritance in cassette builders
If you need to generate almost the same VCR cassette, but with a bit different data, you can do it via inheritance:
Whisperer.define(:robb_stark, parent: :arya_stark) do
response do
body do
factory :robb_stark
end
end
end
In this case all data is taken from aray_stark
cassette builder, only the response body is different.
You can redefine any option of VCR cassette:
Whisperer.define(:robb_stark, parent: :arya_stark) do
request do
uri 'http://example.com/users/10'
end
end
Request/Response Headers
While describing headers for a request or response you can use any kind of headers, they are dynamically created:
headers do
content_length 100
content_type 'application/json'
x_requested_with 'XMLHttpRequest'
end
In a cassette it will look like:
Content-Length:
- '100'
Content-Type:
- application/json
X-Requested-With
- XMLHttpRequest
Placeholder for FactoryGirl
Since VCR is used to stub interractions with external services, there is a big chance that you don't have Ruby model to be used for defining factories. In most cases, you don't need them to generate VCR cassettes. Whisperer offers the placeholder class:
FactoryGirl.define do
factory :arya_stark, class: Placeholder do
first_name 'Arya'
last_name 'Stark'
group 'member'
end
end
Placeholder is a simple class inheriting OpenStruct
class:
Placeholder = Class.new(OpenStruct)
It decouples factories from your application.
Note: If you use own models instead of OpenStruct
objects for defining factories, you have to implement attributes
method returning a hash with attributes for your models. Otherwise, the serializers provided by this gem will use all instance variables of your models for serializing them.
Serializers for a response body
When an external API is subbed with VCR, API response has some format like Json, XML or any other formats. Whisperer supports possibility to convert factories into a format your external API uses. Such mechanism is provided by serializers which are used along with building a response body. Whisperer has only 2 serializers:
- json
- multiple json
Json
serializer is used for serializing one single factory:
response do
body do
factory :robb_stark
serializer :json
end
end
The purpose of json
serializer is to convert a given factory into Json format.
Multiple Json
serializer is used for serializing a collection of factories:
body do
factories ['robb_stark', 'ned_stark']
serializer :json_multiple
end
It is very similar to Json
serializer, but in this case it goes through the array, builds factories, serializes a received array of objects.
If you need to define your own serializer, it is very easy to do. At first you need to define your own serializer class inhering Whisperer::Serializes::Base
class:
class MySerializer < Whisperer::Serializers::Base
def serialize
do_something_with(@obj)
end
end
Note: @obj
is an OpenStruct
instance in this example.
Then you need to register the new serializer:
Whisperer.register_serializer(:my_serializer, Serializers::MySerializer)
Now, it can be used as any other serializer:
response do
body do
factory :robb_stark
serializer :my_serializer
end
end
Sub directories to save cassettes
By default all generated cassettes are saved in one directory. It is not convenient when you have a lot of cassettes there. Therefore, there is an option to define own subpath in a cassette builder, that subpath will be used for saving a cassette:
Whisperer.define(:robb_stark) do
response do
body do
factory 'robb_stark'
end
end
sub_path 'starks'
end
If you don't change the path to your cassette, the cassette from the example will be saved in spec/cassettes/vcr_cassettes/starks
directory. It helps you to structure your cassettes.
Default values of cassette builders
There are attributes which you can omit and the gem will provide default values for them. All values listed in the following example are default and you can omit them:
Whisperer.define(:arya_stark) do
request do
method :get
body do
encoding 'UTF-8'
string ''
end
end
response do
status do
code 200
'OK'
end
body do
encoding 'UTF-8'
serializer :json
end
end
end
Also, there are attributes which are automatically calculated if you don't specify values for them:
- recorded_at - it gets a date of generating a cassette;
- content_length header of a response` - the gem calculates this value based on a response body.
Configuration
You can configure Whisperer through .whisperer.yml
which should be created in a root directory of your project. It gives you following options:
- generate_to - the path to save generated cassettes
- builders_matcher - the pattern to find builders
- factories_matcher - the pattern to find factories
Example of such file:
generate_to: 'spec/cassettes/vcr_cassettes/'
builders_matcher: './spec/cassette_builders/**/*.rb'
factories_matcher: './spec/factories/*.rb'
Generating cassettes
To generate cassettes based on cassette builders, you need to launch command:
$ rake whisperer:cassettes:generate_all
This command will generate new cassettes and re-generate all existing cassettes for VCR.
To generate one particular cassette, you can use this command
$ rake whisperer:cassettes:generate[cassette_builder]
cassette_builder
is a name of the cassette builder.
Generating a sample for the cassette builder
Manual creation of cassette builders is painful. There is a command which can help you with that:
$ rake whisperer:cassettes:builders:sample
It creates a sample for you in the directory with cassette builders, you need to edit it only.
Code examples
There is a repository with examples of the Whisperer gem usage. It will help you to start using it.
Resources
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request