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.
- 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.
- Ruby 2.0.x or 2.1.x
Add this line to your application's Gemfile:
And then execute:
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
Describing VCR cassettes
VCR cassettes are described in
cassette builders. It is Ruby DSL which repeats structure of VCR cassette:
.(: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:
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
ned_stark are taken to generate the response body:
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:
.(: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:
.(:robb_stark, parent: :arya_stark) do request do uri 'http://example.com/users/10' end end
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: do first_name 'Arya' last_name 'Stark' group 'member' end end
Placeholder is a simple class inheriting
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:
- 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
jsonserializer 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
class MySerializer < :::: def serialize do_something_with(@obj) end end
@obj is an
OpenStruct instance in this example.
Then you need to register the new serializer:
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:
.(: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:
.(: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.
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'
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.
There is a repository with examples of the Whisperer gem usage. It will help you to start using it.
- 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