Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Jorge fernando alves feitoza
Jorge fernando alves feitoza

Posted on • Edited on

     

Configure VCR with RSpec

In development, soon or later we need to do some integration with a third-party service and usually this means perform http requests.

As a good developer, we'll add specs in our codebase to make sure that everything is well tested.

In order to keep our test suite faster and consistent, we need to mock our http requests. A simple and good way to achieve this is using theWebmock gem which allow easily mock the http responses for your requests.

# spec/webmock_spec.rbstub_request(:get,'https://example.com/service').to_return(status:200body:'{"a": "abc"}')
Enter fullscreen modeExit fullscreen mode

Example of mock using Webmock

And this works well, but if we need perform many requests to accomplish a task or business logic, just mocking become complicated and hard to maintain.

Example: Lets imagine that we need import some products in our system, but these products are separeted in different categories and to get the products we need get the all the categories, iterate over them to retrieve the products:

let(:categories_endpoint){"https://example.com/categories"}let(:categories_response)do[{id:'category-one-id'},{id:'category-two-id',name:''}].to_jsonendlet(:first_category_endpoint){"#{categories_endpoint}/category-one-id"}let(:first_category_response)do{products_links:[{link:'https://example.com/categories/categor-one-id/product-one'}]}.to_jsonendlet(:second_category_endpoint){"#{categories_endpoint}/category-two-id"}let(:second_category_response)do{products_links:[{link:'https://example.com/categories/categor-two-id/other-product'}]}.to_jsonendlet(:first_product_endpoint){'https://example.com/categories/categor-one-id/product-one'}let(:first_product_response){read_fixture('fixtures/products/first_product.json')}let(:second_product_endpoint){'https://example.com/categories/categor-two-id/other-product'}let(:second_product_response){read_fixture('fixtures/products/second_product.json')}beforedostub_request(:get,categories_endpoint).to_return(status:200,body:categories_response)stub_request(:get,first_category_endpoint).to_return(status:200,body:first_category_response)stub_request(:get,second_category_endpoint).to_return(status:200,body:second_category_response)stub_request(:get,first_product_endpoint).to_return(status:200,body:first_product_response)stub_request(:get,second_product_endpoint).to_return(status:200,body:second_product_response)end
Enter fullscreen modeExit fullscreen mode

A possible mock for the example

As we can see the configurations to mock is huge, and if we need add a thirdy category, these configs will increase even more, becoming hard to read and maintain.
Another problem is that we are spending a time creating that could be use in other places in our application.

A way to avoid this is using theVCR gem.

The VCR gem

The VCR is a gem that record http interactions in our test suite and allow replay these interactions in future runs.

VCR usage

The VCR usage is pretty simple, as follows:

# Configure itVCR.configuredo|config|config.cassette_library_dir="fixtures/vcr_cassettes"config.hook_into:webmockend# Uses itit'does something'doVCR.use_cassette('my_cassete')doexpect(do_request.body).toeql({success:true}.to_json)endend
Enter fullscreen modeExit fullscreen mode

The configuration is simple but the usage is a bit annoing, requiring us wrap all the HTTP interactions in blocks, its also break the TDD "configure, execute and assert" pattern.

VCR with RSpec

The VCR provides an integration with RSpec that uses the RSpec's metadata to configure itself. The configuration required to use this feature ishere. But the idea of this article is have fine-grained control over VCR, so we'll not use it.

Fine-grained control of VCR with RSpec

Let's configure the VCR with RSpec to give to us a fine-grained control for us. The configuration follow these steps:

  • VCR regular configuration
  • Configure VCR to don't run on specs which its not required
  • Configure the VCR using RSpecshared_contexts

VCR regular configuration

Just the regular configuration, as follows the VCR docs.

require'vcr'VCR.configuredo|c|c.cassette_library_dir='spec/vcr_cassettes'c.hook_into:webmockend
Enter fullscreen modeExit fullscreen mode

Disable VCR in specs which its not required

This is not exactly required, but its a good practice, also will make our tests fail if they perform some HTTP requests or use regular mocks without problems.

The VCR has an methodturned_off that accepts a block of code which to be executed without the VCR. So to disable the VCR on specs which its not required, we'll use the RSpec hookaround:

# specs/spec_helper.rbRSpec.configuredo|config|config.arounddo|example|# Just disable the VCR, the configuration for its usage# will be done in a shared_contextifexample.metadata[:vcr]example.runelseVCR.turned_off{example.run}endendend
Enter fullscreen modeExit fullscreen mode

Configure VCR's shared_context

The RSpec'sshared_context will allow us enable the VCR only when we need:

shared_context'with vcr',vcr:truedoarounddo|example|VCR.turn_on!VCR.use_cassette(cassette_name)doexample.runendVCR.turn_off!endend
Enter fullscreen modeExit fullscreen mode

With thisshared_context we can use it as follow and the http will be recorded:

describe'using vcr',vcr:truedo# Configure the cassete namelet(:cassete_name){'path/to/the/interaction'}it'record the http interaction'doexpect(do_request.body).toeql({success:true}.to_json)endit'reuse the same cassete here'doexpect(do_request.headers).toinclude('x-custom-header'=>'abc')endend
Enter fullscreen modeExit fullscreen mode

Improving shared_context

The VCRuse_cassete method accepts many other options, like therecord_mode for example. Using theshared_context andlet allow us configure the VCR to record new interactions in development but raise an error on CI, for example:

shared_context'with vcr',vcr:truedo# Disable new records on CI. Most of the CI providers# configure environment variable called CI.let(:cassette_record){ENV['CI']?:none::new_episodes}arounddo|example|VCR.turn_on!VCR.use_cassette(cassette_name,{record:cassette_record})doexample.runendVCR.turn_off!endend
Enter fullscreen modeExit fullscreen mode

Creating specific shared_contexts

It is possible create specificshared_context that configure the VCR for specific cases. For example, imagine that you need not ignore the headers for some specific requests.

shared_context'with vcr matching headers',vcr_matching_headers:truedoarounddo|example|VCR.turn_on!VCR.use_cassette(cassette_name,{match_requests_on:[:method,:uri,:headers]})doexample.runendVCR.turn_off!endend
Enter fullscreen modeExit fullscreen mode

Conclusion

There are more options on VCR that we could add, but the examples gives the idea about how to control the VCR in our test suite.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I'm a web developer that want know all that I can
  • Location
    Goiania/GO - Brazil
  • Work
    Full-Stack developer
  • Joined

Trending onDEV CommunityHot

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp