{"record":{"id":"655efd82c878ca70","repo":"bblimke/webmock","slug":"webmock-does-not-support-matching-body-for-multipa","errorCode":null,"errorMessage":"WebMock does not support matching body for multipart/form-data requests yet :(","messagePattern":"WebMock does not support matching body for multipart/form-data requests yet :\\(","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/webmock/request_pattern.rb","lineNumber":326,"sourceCode":"      when :json then\n        WebMock::Util::Parsers::JSON.parse(body)\n      when :xml then\n        WebMock::Util::Parsers::XML.parse(body)\n      else\n        WebMock::Util::QueryMapper.query_to_values(body, notation: Config.instance.query_values_notation)\n      end\n    rescue WebMock::Util::Parsers::ParseError\n      nil\n    end\n\n    def body_format(content_type)\n      normalized_content_type = content_type.sub(/\\A(application\\/)[a-zA-Z0-9.-]+\\+(json|xml)\\Z/,'\\1\\2')\n      BODY_FORMATS[normalized_content_type]\n    end\n\n    def assert_non_multipart_body(content_type)\n      if content_type =~ %r{^multipart/form-data}\n        raise ArgumentError.new(\"WebMock does not support matching body for multipart/form-data requests yet :(\")\n      end\n    end\n\n    # Compare two hashes for equality\n    #\n    # For two hashes to match they must have the same length and all\n    # values must match when compared using `#===`.\n    #\n    # The following hashes are examples of matches:\n    #\n    #     {a: /\\d+/} and {a: '123'}\n    #\n    #     {a: '123'} and {a: '123'}\n    #\n    #     {a: {b: /\\d+/}} and {a: {b: '123'}}\n    #\n    #     {a: {b: 'wow'}} and {a: {b: 'wow'}}\n    #","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/bblimke/webmock/blob/b187df8827e1f08a1684a8ddc0a5050dbd449c16/lib/webmock/request_pattern.rb#L308-L344","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Drop the body constraint for multipart stubs and match method + URI (+ headers) only: stub_request(:post, url).to_return(status: 201)","Assert the body through a with block, which bypasses BodyPattern: .with { |req| req.body.to_s.include?('avatar.png') }","Inspect the uploaded parts after the call with WebMock.after_request { |request_signature, _response| ... }, reading request_signature.body","If you control the client, make the test send urlencoded (no file) so body: hash matching works"],"exampleFix":"# before\nstub_request(:post, 'https://api.example.com/uploads')\n  .with(body: { title: 'avatar' })  # ArgumentError once the client posts multipart\nRestClient.post 'https://api.example.com/uploads', { title: 'avatar', file: File.new('a.png') }\n\n# after\nstub_request(:post, 'https://api.example.com/uploads').to_return(status: 201)\nRestClient.post 'https://api.example.com/uploads', { title: 'avatar', file: File.new('a.png') }","handlingStrategy":"validation","validationCode":"# Choose the stub shape from the payload: files force multipart, which must not use body:\ndef stub_upload(url, fields)\n  stub = stub_request(:post, url)\n  fields.key?(:file) ? stub : stub.with(body: fields)\nend","typeGuard":null,"tryCatchPattern":"begin\n  assert_requested(:post, url, body: { title: 'avatar' })\nrescue ArgumentError\n  skip 'webmock cannot match multipart bodies; use a with-block assertion instead'\nend","preventionTips":["Never pair body: with clients that send multipart/form-data","Match uploads by URI and inspect bodies in after_request callbacks","Encapsulate stub creation in a helper that knows whether the payload contains files"],"tags":["webmock","multipart","file-upload","request-matching","ruby"],"backgroundTag":"unsupported-content-type","analyzedSha":"b187df8827e1f08a1684a8ddc0a5050dbd449c16","analyzedAt":"2026-08-23T02:55:22.028Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}