DEV Community

Martijn Versluis for Kabisa Software Artisans

Posted on • Originally published at theguild.nl on

Matching multipart request bodies with VCR

When using VCR to test a HTTP multipart request, you might experience mismatching cassettes because of multipart boundaries. Multipart boundaries are most often randomly generated by the HTTP library or browser, so when VCR tries to match a multipart request with a cassette, the boundaries will be different and the match will fail.

However, it is quite simple to replace the random boundaries by fixed ones before matching requests. A multipart request will have a Content-Type header, which not only makes the request recognizable as multipart request but also specifies the exact boundary that is used. In this case I took a cassette from a Ruby project, so the Content-Type looks like this:

multipart/form-data; boundary=----RubyFormBoundaryTsqIBL0iujC5POpr

Given this header we should be able to simply replace the boundaries with a fixed value. VCR has a configuration option called match_request_on, which is an array that accepts symbols for predefined request matchers (see https://relishapp.com/vcr/vcr/v/5-0-0/docs/request-matching) or a callable (Proc, Lambda). I'm not a real fan of using procs for this but as long as we create an object that responds to call we will be fine:

class VCRMultipartBodyMatcher
  MULTIPART_HEADER_MATCHER = %r{^multipart/form-data; boundary=(.+)$}

  def call(request_1, request_2)
    normalized_multipart_body(request_1) == normalized_multipart_body(request_2)
  end

  private

  def normalized_multipart_body(request)
    content_type = (request.headers['Content-Type'] || []).first.to_s

    return request.body unless multipart_request?(content_type)

    boundary = MULTIPART_HEADER_MATCHER.match(content_type)[1]
    request.body.gsub(boundary, '----RubyFormBoundaryTsqIBL0iujC5POpr')
  end

  def multipart_request?(content_type)
    return false if content_type.empty?

    MULTIPART_HEADER_MATCHER.match?(content_type)
  end
end

Now we can tell VCR to use our custom matcher:

VCR.configure do |config|
  config.default_cassette_options = {
    match_requests_on: [:method, :uri, VCRMultipartBodyMatcher.new]
  }
end

Now, when running the test the boundaries of the request and the cassette will be equal, so the cassette will only mismatch if the request parameters differ. đź‘Ť

Do you need help with testing your application? At Kabisa we know a lot about writing good tests. Leave us a message if you would like to get in touch.

Top comments (0)