Do you hate fixtures? I do as well. The purpose of this library is to make your life easier when your application works with an external API and you use VCR to stub that API.
- Describes VCR cassettes with Ruby.
- Describes entities for the response body of VCR cassettes with FactoryGirl.
- Possibility 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.2.x or 2.3.x
Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
$ gem install whisperer
To create the default directory structure and a config file with default options, execute:
$ rake whisperer:install
It will create a
cassette_builders directory in your
spec folder and a
.whisperer.yml file in the root directory of your project.
If you want to create the config file only, execute:
$ rake whisperer:config:create
Describing VCR cassettes
VCR cassettes are described in
cassette builders using a Ruby DSL which repeats the structure of a VCR cassette:
Whisperer.define(:arya_stark) do request do uri 'http://example.com/users/1' method :get end response do status do code 200 message '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
Since this DSL is Ruby, 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 before going on. There are a few ways 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 used to generate the response body:
You can use multiple factories to generate a 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 used to generate the response body:
You can pass factory objects instead of their names (useful when you need to dynamically generate instances of a factory):
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
Inheritance in cassette builders
If you need to generate almost the same VCR cassette, but with slightly 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 the data is taken from the
arya_stark cassette builder, except for the specified response body.
You can redefine any option of a VCR cassette you are inheriting from:
Whisperer.define(: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 interactions with external services, there is a big chance that you won't have a 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
Placeholder = Class.new(OpenStruct)
It decouples factories from your application.
Note: If you use your own models instead of
OpenStruct objects for defining factories, you have to implement an
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 stubbed with VCR, the API response has some format like JSON, XML or any other formats. Whisperer supports converting factories into the format your external API uses. This mechanism is provided by serializers which are used along with building a response body. Whisperer has 2 built-in serializers:
json serializer is used for serializing one single factory:
response do body do factory :robb_stark serializer :json end end
The purpose of the
json serializer is to convert a given factory into JSON format.
json_multiple 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 the
json serializer, but in this case it goes through the array of factories, builds each factory, serializes the received objects, and returns them as an array.
If you need to define your own serializer, it is very easy to do. First, you need to define your own serializer class inheriting the
class MySerializer < Whisperer::Serializers::Base 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. This can be inconvenient when you have a lot of cassettes there. Therefore, there is an option to define your 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 message '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 the following options:
- generate_to - the path to save generated cassettes
- builders_matcher - the pattern to find builders
- factories_matcher - the pattern to find factories
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 the 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