RSpec으로 VCR 구성

18027 단어 vcrrubyrailsrspec
개발 과정에서 조만간 타사 서비스와 일부 통합을 수행해야 하며 이는 일반적으로 http 요청을 수행하는 것을 의미합니다.

좋은 개발자로서 우리는 코드베이스에 사양을 추가하여 모든 것이 제대로 테스트되었는지 확인합니다.

테스트 스위트를 더 빠르고 일관성 있게 유지하려면 http 요청을 모의 처리해야 합니다. 이를 달성하는 간단하고 좋은 방법은 요청에 대한 http 응답을 쉽게 모방할 수 있는 Webmock gem을 사용하는 것입니다.

# spec/webmock_spec.rb

stub_request(:get, 'https://example.com/service').
  to_return(status: 200 body: '{"a": "abc"}')


Example of mock using Webmock



이것은 잘 작동하지만 작업이나 비즈니스 로직을 수행하기 위해 많은 요청을 수행해야 하는 경우 모의가 복잡해지고 유지하기 어려워집니다.

예: 시스템에서 일부 제품을 가져와야 하지만 이러한 제품이 다른 범주로 구분되어 있고 필요한 제품을 얻기 위해 모든 범주를 가져오기 위해 제품을 검색하기 위해 반복한다고 가정해 보겠습니다.

let(:categories_endpoint) { "https://example.com/categories" }

let(:categories_response) do
  [{id: 'category-one-id'}, {id: 'category-two-id', name: ''}].to_json
end

let(: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_json
end

let(: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_json
end

let(: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') }


before do
  stub_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


A possible mock for the example



우리가 볼 수 있듯이 모의 구성이 방대하고 세 번째 범주를 추가해야 하는 경우 이러한 구성이 훨씬 더 증가하여 읽기 및 유지 관리가 어려워집니다.
또 다른 문제는 우리가 응용 프로그램의 다른 위치에서 사용할 수 있는 것을 만드는 데 시간을 보내고 있다는 것입니다.

이를 피하는 방법은 VCR gem 을 사용하는 것입니다.

VCR 보석



VCR은 테스트 스위트에서 http 상호 작용을 기록하고 향후 실행에서 이러한 상호 작용을 재생할 수 있도록 하는 보석입니다.

VCR 사용



VCR 사용법은 다음과 같이 매우 간단합니다.

# Configure it

VCR.configure do |config|
  config.cassette_library_dir = "fixtures/vcr_cassettes"
  config.hook_into :webmock
end

# Uses it

it 'does something' do
  VCR.use_cassette('my_cassete') do
    expect(do_request.body).to eql({success: true}.to_json)
  end
end


구성은 간단하지만 사용법이 약간 짜증나서 모든 HTTP 상호 작용을 블록으로 래핑해야 하며 TDD "구성, 실행 및 어설션"패턴도 깨뜨립니다.

RSpec이 있는 VCR



VCR은 자체 구성을 위해 RSpec의 메타데이터를 사용하는 RSpec과의 통합을 제공합니다. 이 기능을 사용하는 데 필요한 구성은 here 입니다. 그러나 이 기사의 아이디어는 VCR을 세밀하게 제어하는 ​​것이므로 사용하지 않겠습니다.

RSpec으로 VCR의 세밀한 제어



RSpec을 사용하여 VCR을 구성하여 세밀한 제어가 가능하도록 합시다. 구성은 다음 단계를 따릅니다.
  • VCR 일반 구성
  • VCR이 필요하지 않은 사양에서 실행되지 않도록 구성
  • RSpec을 사용하여 VCR 구성shared_contexts

  • VCR 일반 구성



    VCR 문서에 따라 일반적인 구성입니다.

    require 'vcr'
    
    VCR.configure do |c|
      c.cassette_library_dir = 'spec/vcr_cassettes'
      c.hook_into :webmock
    end
    


    필요하지 않은 사양에서 VCR 비활성화



    꼭 필요한 것은 아니지만 좋은 습관이며 일부 HTTP 요청을 수행하거나 문제 없이 일반 모의 객체를 사용하는 경우 테스트가 실패하게 됩니다.

    VCR에는 VCR 없이 실행할 코드 블록을 허용하는 메서드turned_off가 있습니다. 따라서 필요하지 않은 사양에서 VCR을 비활성화하려면 RSpec 후크around를 사용합니다.

    # specs/spec_helper.rb
    
    RSpec.configure do |config|
      config.around do |example|
    
        # Just disable the VCR, the configuration for its usage
        # will be done in a shared_context
        if example.metadata[:vcr]
          example.run
        else
          VCR.turned_off { example.run }
        end
      end
    end
    


    VCR의 shared_context 구성



    RSpecshared_context에서는 다음이 필요할 때만 VCR을 활성화할 수 있습니다.

    shared_context 'with vcr', vcr: true do
      around do |example|
        VCR.turn_on!
    
        VCR.use_cassette(cassette_name) do
          example.run
        end
    
        VCR.turn_off!
      end
    end
    


    이를 통해shared_context 다음과 같이 사용할 수 있으며 http가 기록됩니다.

    describe 'using vcr', vcr: true do
      # Configure the cassete name
      let(:cassete_name) { 'path/to/the/interaction' }
    
      it 'record the http interaction' do
        expect(do_request.body).to eql({ success: true }.to_json)
      end
    
      it 'reuse the same cassete here' do
        expect(do_request.headers).to include('x-custom-header' => 'abc')
      end
    end
    
    


    shared_context 개선



    VCRuse_cassete 방법은 예를 들어 record_mode와 같은 다른 많은 옵션을 허용합니다. shared_contextlet를 사용하면 개발 중인 새 상호 작용을 기록하도록 VCR을 구성할 수 있지만 CI에서 오류가 발생합니다. 예를 들면 다음과 같습니다.

    shared_context 'with vcr', vcr: true do
      # Disable new records on CI. Most of the CI providers
      # configure environment variable called CI.
      let(:cassette_record) { ENV['CI'] ? :none : :new_episodes }
    
      around do |example|
        VCR.turn_on!
    
        VCR.use_cassette(cassette_name, { record: cassette_record }) do
          example.run
        end
    
        VCR.turn_off!
      end
    end
    


    특정 shared_context 만들기



    특정 사례에 대해 VCR을 구성하는 특정shared_context을 만들 수 있습니다. 예를 들어 일부 특정 요청에 대한 헤더를 무시할 필요가 없다고 가정합니다.

    shared_context 'with vcr matching headers', vcr_matching_headers: true do
      around do |example|
        VCR.turn_on!
    
        VCR.use_cassette(cassette_name, { match_requests_on: [:method, :uri, :headers]}) do
          example.run
        end
    
        VCR.turn_off!
      end
    end
    


    결론



    추가할 수 있는 VCR에는 더 많은 옵션이 있지만 예제는 테스트 세트에서 VCR을 제어하는 ​​방법에 대한 아이디어를 제공합니다.

    좋은 웹페이지 즐겨찾기