bblimke/webmock · error · ArgumentError

WebMock does not support matching body for multipart/form-da

Error message

WebMock does not support matching body for multipart/form-data requests yet :(

What it means

When a stub or assertion narrows on body:, BodyPattern#matches? first inspects the incoming request's Content-Type; if it starts with multipart/form-data it raises ArgumentError from lib/webmock/request_pattern.rb:324 because webmock has no multipart parser to turn the body into a comparable hash. It fires at match time - the moment a multipart request is actually made (or verified) against a stub that carries a body constraint - not when the stub is declared. Dropping the body constraint avoids the check entirely.

Source

Thrown at lib/webmock/request_pattern.rb:326

      when :json then
        WebMock::Util::Parsers::JSON.parse(body)
      when :xml then
        WebMock::Util::Parsers::XML.parse(body)
      else
        WebMock::Util::QueryMapper.query_to_values(body, notation: Config.instance.query_values_notation)
      end
    rescue WebMock::Util::Parsers::ParseError
      nil
    end

    def body_format(content_type)
      normalized_content_type = content_type.sub(/\A(application\/)[a-zA-Z0-9.-]+\+(json|xml)\Z/,'\1\2')
      BODY_FORMATS[normalized_content_type]
    end

    def assert_non_multipart_body(content_type)
      if content_type =~ %r{^multipart/form-data}
        raise ArgumentError.new("WebMock does not support matching body for multipart/form-data requests yet :(")
      end
    end

    # Compare two hashes for equality
    #
    # For two hashes to match they must have the same length and all
    # values must match when compared using `#===`.
    #
    # The following hashes are examples of matches:
    #
    #     {a: /\d+/} and {a: '123'}
    #
    #     {a: '123'} and {a: '123'}
    #
    #     {a: {b: /\d+/}} and {a: {b: '123'}}
    #
    #     {a: {b: 'wow'}} and {a: {b: 'wow'}}
    #

View on GitHub (pinned to b187df8827)

Solutions

  1. Drop the body constraint for multipart stubs and match method + URI (+ headers) only: stub_request(:post, url).to_return(status: 201)
  2. Assert the body through a with block, which bypasses BodyPattern: .with { |req| req.body.to_s.include?('avatar.png') }
  3. Inspect the uploaded parts after the call with WebMock.after_request { |request_signature, _response| ... }, reading request_signature.body
  4. If you control the client, make the test send urlencoded (no file) so body: hash matching works

Example fix

# before
stub_request(:post, 'https://api.example.com/uploads')
  .with(body: { title: 'avatar' })  # ArgumentError once the client posts multipart
RestClient.post 'https://api.example.com/uploads', { title: 'avatar', file: File.new('a.png') }

# after
stub_request(:post, 'https://api.example.com/uploads').to_return(status: 201)
RestClient.post 'https://api.example.com/uploads', { title: 'avatar', file: File.new('a.png') }
Defensive patterns

Strategy: validation

Validate before calling

# Choose the stub shape from the payload: files force multipart, which must not use body:
def stub_upload(url, fields)
  stub = stub_request(:post, url)
  fields.key?(:file) ? stub : stub.with(body: fields)
end

Try / catch

begin
  assert_requested(:post, url, body: { title: 'avatar' })
rescue ArgumentError
  skip 'webmock cannot match multipart bodies; use a with-block assertion instead'
end

Prevention

When it happens

Trigger: stub_request(:post, url).with(body: { name: 'x' }) combined with code posting multipart: Faraday with faraday-multipart middleware, RestClient.post(url, file: File.new(...)), HTTParty or Typhoeus attaching a file, or any form helper switching to multipart once a file field exists. Also assert_requested(:post, url, body: {...}) verified against a multipart request.

Common situations: File-upload specs that started as plain urlencoded form posts (where body: hash matching works) and gained a file attachment later; third-party API clients that always send multipart; a request-library upgrade changing the default encoding.

Related errors


AI-assisted analysis of bblimke/webmock@b187df8827 (2026-08-23). Data as JSON: /api/errors/655efd82c878ca70. Report an issue: GitHub.