Webmock
Library for stubbing and setting expectations on HTTP requests in Ruby.
Install / Use
/learn @bblimke/WebmockREADME
WebMock
Library for stubbing and setting expectations on HTTP requests in Ruby.
Features
- Stubbing HTTP requests at low http client lib level (no need to change tests when you change HTTP library)
- Setting and verifying expectations on HTTP requests
- Matching requests based on method, URI, headers and body
- Smart matching of the same URIs in different representations (also encoded and non encoded forms)
- Smart matching of the same headers in different representations.
- Support for Test::Unit
- Support for RSpec
- Support for MiniTest
Supported HTTP libraries
- Async::HTTP::Client
- Curb (currently only Curb::Easy)
- EM-HTTP-Request
- Excon
- HTTPClient
- HTTP Gem (also known as http.rb)
- httpx
- Manticore
- Net::HTTP and other libraries based on Net::HTTP, e.g.:
- Patron
- Typhoeus (currently only Typhoeus::Hydra)
Supported Ruby Interpreters
- MRI 2.6
- MRI 2.7
- MRI 3.0
- MRI 3.1
- MRI 3.2
- MRI 3.3
- MRI 3.4
- MRI 4.0
- JRuby
Installation
gem install webmock
or alternatively:
# add to your Gemfile
group :test do
gem "webmock"
end
or to install the latest development version from github master
git clone http://github.com/bblimke/webmock.git
cd webmock
rake install
Upgrading from v1.x to v2.x
WebMock 2.x has changed somewhat since version 1.x. Changes are listed in CHANGELOG.md
Cucumber
Create a file features/support/webmock.rb with the following contents:
require 'webmock/cucumber'
MiniTest
Add the following code to test/test_helper:
require 'webmock/minitest'
RSpec
Add the following code to spec/spec_helper:
require 'webmock/rspec'
Test::Unit
Add the following code to test/test_helper.rb
require 'webmock/test_unit'
Outside a test framework
You can also use WebMock outside a test framework:
require 'webmock'
include WebMock::API
WebMock.enable!
Examples
Stubbing
Stubbed request based on uri only and with the default response
stub_request(:any, "www.example.com")
Net::HTTP.get("www.example.com", "/") # ===> Success
Stubbing requests based on method, uri, body and headers
stub_request(:post, "www.example.com").
with(body: "abc", headers: { 'Content-Length' => 3 })
uri = URI.parse("http://www.example.com/")
req = Net::HTTP::Post.new(uri.path)
req['Content-Length'] = 3
res = Net::HTTP.start(uri.host, uri.port) do |http|
http.request(req, "abc")
end # ===> Success
Matching request body and headers against regular expressions
stub_request(:post, "www.example.com").
with(body: /world$/, headers: {"Content-Type" => /image\/.+/}).
to_return(body: "abc")
uri = URI.parse('http://www.example.com/')
req = Net::HTTP::Post.new(uri.path)
req['Content-Type'] = 'image/png'
res = Net::HTTP.start(uri.host, uri.port) do |http|
http.request(req, 'hello world')
end # ===> Success
Matching request body against a hash. Body can be URL-Encoded, JSON or XML.
stub_request(:post, "www.example.com").
with(body: {data: {a: '1', b: 'five'}})
RestClient.post('www.example.com', "data[a]=1&data[b]=five",
content_type: 'application/x-www-form-urlencoded') # ===> Success
RestClient.post('www.example.com', '{"data":{"a":"1","b":"five"}}',
content_type: 'application/json') # ===> Success
RestClient.post('www.example.com', '<data a="1" b="five" />',
content_type: 'application/xml') # ===> Success
Matching request body against partial hash.
stub_request(:post, "www.example.com").
with(body: hash_including({data: {a: '1', b: 'five'}}))
RestClient.post('www.example.com', "data[a]=1&data[b]=five&x=1",
:content_type => 'application/x-www-form-urlencoded') # ===> Success
Matching custom request headers
stub_request(:any, "www.example.com").
with(headers:{ 'Header-Name' => 'Header-Value' })
uri = URI.parse('http://www.example.com/')
req = Net::HTTP::Post.new(uri.path)
req['Header-Name'] = 'Header-Value'
res = Net::HTTP.start(uri.host, uri.port) do |http|
http.request(req, 'abc')
end # ===> Success
Matching multiple headers with the same name
stub_request(:get, 'www.example.com').
with(headers: {'Accept' => ['image/jpeg', 'image/png'] })
req = Net::HTTP::Get.new("/")
req['Accept'] = ['image/png']
req.add_field('Accept', 'image/jpeg')
Net::HTTP.start("www.example.com") {|http| http.request(req) } # ===> Success
Matching requests against provided block
stub_request(:post, "www.example.com").with { |request| request.body == "abc" }
RestClient.post('www.example.com', 'abc') # ===> Success
Request with basic authentication header
stub_request(:get, "www.example.com").with(basic_auth: ['user', 'pass'])
# or
# stub_request(:get, "www.example.com").
# with(headers: {'Authorization' => "Basic #{ Base64.strict_encode64('user:pass').chomp}"})
Net::HTTP.start('www.example.com') do |http|
req = Net::HTTP::Get.new('/')
req.basic_auth 'user', 'pass'
http.request(req)
end # ===> Success
Important! Since version 2.0.0, WebMock does not match credentials provided in Authorization header and credentials provided in the userinfo of a url. I.e. stub_request(:get, "user:pass@www.example.com") does not match a request with credentials provided in the Authorization header.
Request with basic authentication in the url
stub_request(:get, "user:pass@www.example.com")
RestClient.get('user:pass@www.example.com') # ===> Success
Matching uris using regular expressions
stub_request(:any, /example/)
Net::HTTP.get('www.example.com', '/') # ===> Success
Matching uris using lambda
stub_request(:any, ->(uri) { true })
Matching uris using RFC 6570 - Basic Example
uri_template = Addressable::Template.new "www.example.com/{id}/"
stub_request(:any, uri_template)
Net::HTTP.get('www.example.com', '/webmock/') # ===> Success
Matching uris using RFC 6570 - Advanced Example
uri_template =
Addressable::Template.new "www.example.com/thing/{id}.json{?x,y,z}{&other*}"
stub_request(:any, uri_template)
Net::HTTP.get('www.example.com',
'/thing/5.json?x=1&y=2&z=3&anyParam=4') # ===> Success
Matching query params using hash
stub_request(:get, "www.example.com").with(query: {"a" => ["b", "c"]})
RestClient.get("http://www.example.com/?a[]=b&a[]=c") # ===> Success
Matching partial query params using hash
stub_request(:get, "www.example.com").
with(query: hash_including({"a" => ["b", "c"]}))
RestClient.get("http://www.example.com/?a[]=b&a[]=c&x=1") # ===> Success
Matching partial query params using hash_excluding
stub_request(:get, "www.example.com").
with(query: hash_excluding({"a" => "b"}))
RestClient.get("http://www.example.com/?a=b") # ===> Failure
RestClient.get("http://www.example.com/?a=c") # ===> Success
Stubbing with custom response
stub_request(:any, "www.example.com").
to_return(body: "abc", status: 200,
headers: { 'Content-Length' => 3 })
Net::HTTP.get("www.example.com", '/') # ===> "abc"
Set appropriate Content-Type for HTTParty's parsed_response.
stub_request(:any, "www.example.com").to_return body: '{}', headers: {content_type: 'application/json'}
Response with body specified as IO object
File.open('/tmp/response_body.txt', 'w') { |f| f.puts 'abc' }
stub_request(:any, "www.example.com").
to_return(body: File.new('/tmp/response_body.txt'), status: 200)
Net::HTTP.get('www.example.com', '/') # ===> "abc\n"
Response with JSON body
stub_request(:any, "www.example.com").
to_return_json(body: {foo: "bar"})
Net::HTTP.get('www.example.com', '/') # ===> "{\"foo\": \"bar\"}"
Response with custom status message
stub_request(:any, "www.example.com").
to_return(status: [500, "Internal Server Error"])
req = Net::HTTP::Get.new("/")
Net::HTTP.start("www.example.com") { |http| http.request(req) }.
message # ===> "Internal Server Error"
Replaying raw responses recorded with curl -is
curl -is www.example.com > /tmp/example_curl_-is_output.txt
raw_response_file = File.new("/tmp/example_curl_-is_output.txt")
from file
stub_request(:get, "www.example.com").to_return(raw_response_file)
or string
stub_request(:get, "www.example.com").to_return(raw_response_file.read)
Responses dynamically evaluated from block
stub_request(:any, 'www.example.net').
to_return { |request| {body: request.body} }
RestClient.post('www.example.net', 'abc') # ===> "abc\n"
