webmock 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,60 @@
1
1
  #Changelog
2
2
 
3
+ ## 1.3.0
4
+
5
+ * Added support for [em-http-request](http://github.com/igrigorik/em-http-request)
6
+
7
+ * Matching query params using a hash
8
+
9
+ stub_http_request(:get, "www.example.com").with(:query => {"a" => ["b", "c"]})
10
+
11
+ RestClient.get("http://www.example.com/?a[]=b&a[]=c") # ===> Success
12
+
13
+ request(:get, "www.example.com").with(:query => {"a" => ["b", "c"]}).should have_been_made # ===> Success
14
+
15
+ * Matching request body against a hash. Body can be URL-Encoded, JSON or XML.
16
+ (Thanks to Steve Tooke for the idea and a solution for url-encoded bodies)
17
+
18
+ stub_http_request(:post, "www.example.com").
19
+ with(:body => {:data => {:a => '1', :b => 'five'}})
20
+
21
+ RestClient.post('www.example.com', "data[a]=1&data[b]=five",
22
+ :content_type => 'application/x-www-form-urlencoded') # ===> Success
23
+
24
+ RestClient.post('www.example.com', '{"data":{"a":"1","b":"five"}}',
25
+ :content_type => 'application/json') # ===> Success
26
+
27
+ RestClient.post('www.example.com', '<data a="1" b="five" />',
28
+ :content_type => 'application/xml' ) # ===> Success
29
+
30
+ request(:post, "www.example.com").
31
+ with(:body => {:data => {:a => '1', :b => 'five'}},
32
+ :headers => 'Content-Type' => 'application/json').should have_been_made # ===> Success
33
+
34
+ * Request callbacks (Thanks to Myron Marston for all suggestions)
35
+
36
+ WebMock can now invoke callbacks for stubbed or real requests:
37
+
38
+ WebMock.after_request do |request_signature, response|
39
+ puts "Request #{request_signature} was made and #{response} was returned"
40
+ end
41
+
42
+ invoke callbacks for real requests only and except requests made with Patron client
43
+
44
+ WebMock.after_request(:except => [:patron], :real_requests_only => true) do |request_signature, response|
45
+ puts "Request #{request_signature} was made and #{response} was returned"
46
+ end
47
+
48
+ * `to_raise()` now accepts an exception instance or a string as argument in addition to an exception class
49
+
50
+ stub_request(:any, 'www.example.net').to_raise(StandardError.new("some error"))
51
+
52
+ stub_request(:any, 'www.example.net').to_raise("some error")
53
+
54
+ * Matching request stubs based on a URI is 30% faster
55
+
56
+ * Fixed constant namespace issues in HTTPClient adapter. Thanks to Nathaniel Bibler for submitting a patch.
57
+
3
58
  ## 1.2.2
4
59
 
5
60
  * Fixed problem where ArgumentError was raised if query params were made up of an array e.g. data[]=a&data[]=b. Thanks to Steve Tooke
data/README.md CHANGED
@@ -13,10 +13,14 @@ Features
13
13
  * Smart matching of the same headers in different representations.
14
14
  * Support for Test::Unit
15
15
  * Support for RSpec 1.x and RSpec 2.x
16
- * Support for Net::HTTP and other http libraries based on Net::HTTP (i.e RightHttpConnection, rest-client, HTTParty)
17
- * Support for HTTPClient library (both sync and async requests)
18
- * Support for Patron library
19
- * Easy to extend to other HTTP libraries
16
+
17
+ Supported HTTP libraries
18
+ ------------------------
19
+
20
+ * Net::HTTP and libraries based on Net::HTTP (i.e RightHttpConnection, REST Client, HTTParty)
21
+ * HTTPClient
22
+ * Patron
23
+ * EM-HTTP-Request
20
24
 
21
25
  Installation
22
26
  ------------
@@ -80,6 +84,20 @@ You can also use WebMock without RSpec or Test::Unit support:
80
84
  res = Net::HTTP.start(uri.host, uri.port) {|http|
81
85
  http.request(req, 'hello world')
82
86
  } # ===> Success
87
+
88
+ ### Matching request body against a hash. Body can be URL-Encoded, JSON or XML.
89
+
90
+ stub_http_request(:post, "www.example.com").
91
+ with(:body => {:data => {:a => '1', :b => 'five'}})
92
+
93
+ RestClient.post('www.example.com', "data[a]=1&data[b]=five",
94
+ :content_type => 'application/x-www-form-urlencoded') # ===> Success
95
+
96
+ RestClient.post('www.example.com', '{"data":{"a":"1","b":"five"}}',
97
+ :content_type => 'application/json') # ===> Success
98
+
99
+ RestClient.post('www.example.com', '<data a="1" b="five" />',
100
+ :content_type => 'application/xml' ) # ===> Success
83
101
 
84
102
  ### Matching custom request headers
85
103
 
@@ -121,7 +139,13 @@ You can also use WebMock without RSpec or Test::Unit support:
121
139
  stub_request(:any, /.*example.*/)
122
140
 
123
141
  Net::HTTP.get('www.example.com', '/') # ===> Success
142
+
143
+ ### Matching query params using hash
124
144
 
145
+ stub_http_request(:get, "www.example.com").with(:query => {"a" => ["b", "c"]})
146
+
147
+ RestClient.get("http://www.example.com/?a[]=b&a[]=c") # ===> Success
148
+
125
149
  ### Stubbing with custom response
126
150
 
127
151
  stub_request(:any, "www.example.com").to_return(:body => "abc", :status => 200, :headers => { 'Content-Length' => 3 } )
@@ -179,9 +203,19 @@ You can also use WebMock without RSpec or Test::Unit support:
179
203
 
180
204
  ### Raising errors
181
205
 
206
+ #### Exception declared by class
207
+
182
208
  stub_request(:any, 'www.example.net').to_raise(StandardError)
183
209
 
184
210
  RestClient.post('www.example.net', 'abc') # ===> StandardError
211
+
212
+ #### or by exception instance
213
+
214
+ stub_request(:any, 'www.example.net').to_raise(StandardError.new("some error"))
215
+
216
+ #### or by string
217
+
218
+ stub_request(:any, 'www.example.net').to_raise("some error")
185
219
 
186
220
  ### Raising timeout errors
187
221
 
@@ -282,6 +316,11 @@ You can also use WebMock without RSpec or Test::Unit support:
282
316
  WebMock.should_not have_requested(:get, "www.something.com")
283
317
 
284
318
  WebMock.should have_requested(:post, "www.example.com").with { |req| req.body == "abc" }
319
+
320
+ WebMock.should have_requested(:get, "www.example.com").with(:query => {"a" => ["b", "c"]})
321
+
322
+ WebMock.should have_requested(:get, "www.example.com").
323
+ with(:body => {"a" => ["b", "c"]}, :headers => 'Content-Type' => 'application/json')
285
324
 
286
325
  ### Different way of setting expectations in RSpec
287
326
 
@@ -293,6 +332,11 @@ You can also use WebMock without RSpec or Test::Unit support:
293
332
 
294
333
  request(:post, "www.example.com").with { |req| req.body == "abc" }.should have_been_made
295
334
 
335
+ request(:get, "www.example.com").with(:query => {"a" => ["b", "c"]}).should have_been_made
336
+
337
+ request(:post, "www.example.com").
338
+ with(:body => {"a" => ["b", "c"]}, :headers => 'Content-Type' => 'application/json').should have_been_made
339
+
296
340
  ## Clearing stubs and request history
297
341
 
298
342
  If you want to reset all current stubs and history of requests use `WebMock.reset_webmock`
@@ -402,6 +446,20 @@ i.e the following two sets of headers are equal:
402
446
 
403
447
  To record your application's real HTTP interactions and replay them later in tests you can use [VCR](http://github.com/myronmarston/vcr) with WebMock.
404
448
 
449
+ ## Request callbacks
450
+
451
+ ####WebMock can invoke callbacks stubbed or real requests:
452
+
453
+ WebMock.after_request do |request_signature, response|
454
+ puts "Request #{request_signature} was made and #{response} was returned"
455
+ end
456
+
457
+ #### invoke callbacks for real requests only and except requests made with Patron
458
+
459
+ WebMock.after_request(:except => [:patron], :real_requests_only => true) do |request_signature, response|
460
+ puts "Request #{request_signature} was made and #{response} was returned"
461
+ end
462
+
405
463
  ## Bugs and Issues
406
464
 
407
465
  Please submit them here [http://github.com/bblimke/webmock/issues](http://github.com/bblimke/webmock/issues)
@@ -439,6 +497,7 @@ People who submitted patches and new features or suggested improvements. Many th
439
497
  * Jose Angel Cortinas
440
498
  * Razic
441
499
  * Steve Tooke
500
+ * Nathaniel Bibler
442
501
 
443
502
  ## Background
444
503
 
data/Rakefile CHANGED
@@ -11,9 +11,11 @@ begin
11
11
  gem.homepage = "http://github.com/bblimke/webmock"
12
12
  gem.authors = ["Bartosz Blimke"]
13
13
  gem.add_dependency "addressable", ">= 2.1.1"
14
+ gem.add_dependency "crack", ">=0.1.7"
14
15
  gem.add_development_dependency "rspec", ">= 1.2.9"
15
16
  gem.add_development_dependency "httpclient", ">= 2.1.5.2"
16
17
  gem.add_development_dependency "patron", ">= 0.4.5" unless RUBY_PLATFORM =~ /java/
18
+ gem.add_development_dependency "em-http-request", ">= 0.2.7" unless RUBY_PLATFORM =~ /java/
17
19
  end
18
20
  Jeweler::GemcutterTasks.new
19
21
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.2
1
+ 1.3.0
@@ -1,10 +1,12 @@
1
1
  require 'singleton'
2
2
 
3
3
  require 'addressable/uri'
4
+ require 'crack'
4
5
 
5
6
  require 'webmock/http_lib_adapters/net_http'
6
7
  require 'webmock/http_lib_adapters/httpclient'
7
8
  require 'webmock/http_lib_adapters/patron'
9
+ require 'webmock/http_lib_adapters/em_http_request'
8
10
 
9
11
  require 'webmock/errors'
10
12
 
@@ -20,5 +22,6 @@ require 'webmock/response'
20
22
 
21
23
  require 'webmock/request_execution_verifier'
22
24
  require 'webmock/config'
25
+ require 'webmock/callback_registry'
23
26
  require 'webmock/request_registry'
24
27
  require 'webmock/webmock'
@@ -0,0 +1,35 @@
1
+ module WebMock
2
+ class CallbackRegistry
3
+ @@callbacks = []
4
+
5
+ def self.add_callback(options, block)
6
+ @@callbacks << {:options => options, :block => block}
7
+ end
8
+
9
+ def self.callbacks
10
+ @@callbacks
11
+ end
12
+
13
+ def self.invoke_callbacks(options, request_signature, response)
14
+ return if @@callbacks.empty?
15
+ CallbackRegistry.callbacks.each do |callback|
16
+ except = callback[:options][:except]
17
+ real_only = callback[:options][:real_requests_only]
18
+ unless except && except.include?(options[:lib])
19
+ if !real_only || options[:real_request]
20
+ callback[:block].call(request_signature, response)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.reset
27
+ @@callbacks = []
28
+ end
29
+
30
+ def self.any_callbacks?
31
+ !@@callbacks.empty?
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,114 @@
1
+ if defined?(EventMachine::HttpRequest)
2
+
3
+ module EventMachine
4
+ class HttpRequest
5
+
6
+ class WebMockFakeHttpClient < EventMachine::HttpClient
7
+
8
+ def setup(response, uri, error = nil)
9
+ @uri = uri
10
+ if error
11
+ on_error(error)
12
+ fail(self)
13
+ else
14
+ receive_data(response)
15
+ succeed(self)
16
+ end
17
+ end
18
+
19
+ def stream(&blk)
20
+ blk.call(@response)
21
+ end
22
+
23
+ def unbind
24
+ end
25
+
26
+ end
27
+
28
+ def send_request_with_webmock(&block)
29
+ request_signature = build_request_signature
30
+
31
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
32
+
33
+ if WebMock.registered_request?(request_signature)
34
+ webmock_response = WebMock.response_for_request(request_signature)
35
+ WebMock::CallbackRegistry.invoke_callbacks(
36
+ {:lib => :em_http_request}, request_signature, webmock_response)
37
+ client = WebMockFakeHttpClient.new(nil)
38
+ client.on_error("WebMock timeout error") if webmock_response.should_timeout
39
+ client.setup(make_raw_response(webmock_response), @uri,
40
+ webmock_response.should_timeout ? "WebMock timeout error" : nil)
41
+ client
42
+ elsif WebMock.net_connect_allowed?(request_signature.uri)
43
+ http = send_request_without_webmock(&block)
44
+ http.callback {
45
+ if WebMock::CallbackRegistry.any_callbacks?
46
+ webmock_response = build_webmock_response(http)
47
+ WebMock::CallbackRegistry.invoke_callbacks(
48
+ {:lib => :em_http_request, :real_request => true}, request_signature,
49
+ webmock_response)
50
+ end
51
+ }
52
+ http
53
+ else
54
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
55
+ end
56
+ end
57
+
58
+ alias_method :send_request_without_webmock, :send_request
59
+ alias_method :send_request, :send_request_with_webmock
60
+
61
+
62
+ private
63
+
64
+ def build_webmock_response(http)
65
+ webmock_response = WebMock::Response.new
66
+ webmock_response.status = [http.response_header.status, http.response_header.http_reason]
67
+ webmock_response.headers = http.response_header
68
+ webmock_response.body = http.response
69
+ webmock_response
70
+ end
71
+
72
+ def build_request_signature
73
+
74
+ if @options[:authorization] || @options['authorization']
75
+ auth = (@options[:authorization] || @options['authorization'])
76
+ userinfo = auth.join(':')
77
+ userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo)
78
+ @options.reject! {|k,v| k.to_s == 'authorization' } #we added it to url userinfo
79
+ @uri.userinfo = userinfo
80
+ end
81
+
82
+ @uri.query_values = (@uri.query_values || {}).merge(@options[:query]) if @options[:query]
83
+
84
+ WebMock::RequestSignature.new(
85
+ @method.downcase.to_sym,
86
+ @uri.to_s,
87
+ :body => (@options[:body] || @options['body']),
88
+ :headers => (@options[:head] || @options['head'])
89
+ )
90
+ end
91
+
92
+
93
+ def make_raw_response(response)
94
+ response.raise_error_if_any
95
+
96
+ status, headers, body = response.status, response.headers, response.body
97
+
98
+ response_string = []
99
+ response_string << "HTTP/1.1 #{status[0]} #{status[1]}"
100
+
101
+ headers.each do |header, value|
102
+ value = value.join(", ") if value.is_a?(Array)
103
+ response_string << "#{header}: #{value}"
104
+ end if headers
105
+
106
+ response_string << "" << body
107
+ response_string.join("\n")
108
+ end
109
+
110
+
111
+ end
112
+ end
113
+
114
+ end
@@ -1,6 +1,6 @@
1
- if defined?(HTTPClient)
1
+ if defined?(::HTTPClient)
2
2
 
3
- class HTTPClient
3
+ class ::HTTPClient
4
4
 
5
5
  def do_get_block_with_webmock(req, proxy, conn, &block)
6
6
  do_get_with_webmock(req, proxy, conn, false, &block)
@@ -18,15 +18,27 @@ if defined?(HTTPClient)
18
18
  if WebMock.registered_request?(request_signature)
19
19
  webmock_response = WebMock.response_for_request(request_signature)
20
20
  response = build_httpclient_response(webmock_response, stream, &block)
21
- conn.push(response)
21
+ res = conn.push(response)
22
+ WebMock::CallbackRegistry.invoke_callbacks(
23
+ {:lib => :http_client}, request_signature, webmock_response)
24
+ res
22
25
  elsif WebMock.net_connect_allowed?(request_signature.uri)
23
- if stream
26
+ res = if stream
24
27
  do_get_stream_without_webmock(req, proxy, conn, &block)
25
28
  else
26
29
  do_get_block_without_webmock(req, proxy, conn, &block)
27
30
  end
31
+ res = conn.pop
32
+ conn.push(res)
33
+ if WebMock::CallbackRegistry.any_callbacks?
34
+ webmock_response = build_webmock_response(res)
35
+ WebMock::CallbackRegistry.invoke_callbacks(
36
+ {:lib => :http_client, :real_request => true}, request_signature,
37
+ webmock_response)
38
+ end
39
+ res
28
40
  else
29
- raise NetConnectNotAllowedError.new(request_signature)
41
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
30
42
  end
31
43
  end
32
44
 
@@ -37,7 +49,7 @@ if defined?(HTTPClient)
37
49
  if WebMock.registered_request?(request_signature) || WebMock.net_connect_allowed?(request_signature.uri)
38
50
  do_request_async_without_webmock(method, uri, query, body, extheader)
39
51
  else
40
- raise NetConnectNotAllowedError.new(request_signature)
52
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
41
53
  end
42
54
  end
43
55
 
@@ -65,9 +77,24 @@ if defined?(HTTPClient)
65
77
  response
66
78
  end
67
79
  end
80
+
81
+ def build_webmock_response(httpclient_response)
82
+ webmock_response = WebMock::Response.new
83
+ webmock_response.status = [httpclient_response.status, httpclient_response.reason]
84
+ webmock_response.headers = httpclient_response.header.all
85
+ if httpclient_response.content.respond_to?(:read)
86
+ webmock_response.body = httpclient_response.content.read
87
+ body = HTTP::Message::Body.new
88
+ body.init_response(StringIO.new(webmock_response.body))
89
+ httpclient_response.body = body
90
+ else
91
+ webmock_response.body = httpclient_response.content
92
+ end
93
+ webmock_response
94
+ end
68
95
 
69
96
  def build_request_signature(req)
70
- uri = Addressable::URI.heuristic_parse(req.header.request_uri.to_s)
97
+ uri = WebMock::Util::URI.heuristic_parse(req.header.request_uri.to_s)
71
98
  uri.query_values = req.header.request_query if req.header.request_query
72
99
  uri.port = req.header.request_uri.port
73
100
  uri = uri.omit(:userinfo)
@@ -1,6 +1,7 @@
1
1
  require 'net/http'
2
2
  require 'net/https'
3
3
  require 'stringio'
4
+ require File.join(File.dirname(__FILE__), 'net_http_response')
4
5
 
5
6
  class StubSocket #:nodoc:
6
7
 
@@ -60,10 +61,20 @@ module Net #:nodoc: all
60
61
  if WebMock.registered_request?(request_signature)
61
62
  @socket = Net::HTTP.socket_type.new
62
63
  webmock_response = WebMock.response_for_request(request_signature)
64
+ WebMock::CallbackRegistry.invoke_callbacks(
65
+ {:lib => :net_http}, request_signature, webmock_response)
63
66
  build_net_http_response(webmock_response, &block)
64
67
  elsif WebMock.net_connect_allowed?(request_signature.uri)
65
68
  connect_without_webmock
66
- request_without_webmock(request, nil, &block)
69
+ response = request_without_webmock(request, nil)
70
+ if WebMock::CallbackRegistry.any_callbacks? && started?
71
+ webmock_response = build_webmock_response(response)
72
+ WebMock::CallbackRegistry.invoke_callbacks(
73
+ {:lib => :net_http, :real_request => true}, request_signature, webmock_response)
74
+ response.extend WebMock::Net::HTTPResponse
75
+ end
76
+ yield response if block_given?
77
+ response
67
78
  else
68
79
  raise WebMock::NetConnectNotAllowedError.new(request_signature)
69
80
  end
@@ -99,11 +110,22 @@ module Net #:nodoc: all
99
110
 
100
111
  response
101
112
  end
113
+
114
+ def build_webmock_response(net_http_response)
115
+ webmock_response = WebMock::Response.new
116
+ webmock_response.status = [
117
+ net_http_response.code.to_i,
118
+ net_http_response.message]
119
+ webmock_response.headers = net_http_response.to_hash
120
+ webmock_response.body = net_http_response.body
121
+ webmock_response
122
+ end
102
123
 
103
124
  end
104
125
 
105
126
  end
106
127
 
128
+
107
129
  module WebMock
108
130
  module NetHTTPUtility
109
131
 
@@ -111,7 +133,7 @@ module WebMock
111
133
  protocol = net_http.use_ssl? ? "https" : "http"
112
134
 
113
135
  path = request.path
114
- path = Addressable::URI.heuristic_parse(request.path).request_uri if request.path =~ /^http/
136
+ path = WebMock::Util::URI.heuristic_parse(request.path).request_uri if request.path =~ /^http/
115
137
 
116
138
  if request["authorization"] =~ /^Basic /
117
139
  userinfo = WebMock::Util::Headers.decode_userinfo_from_header(request["authorization"])