webmock 0.7.3 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +15 -0
- data/README.md +80 -68
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/webmock.rb +2 -0
- data/lib/webmock/adapters/rspec/webmock_matcher.rb +1 -4
- data/lib/webmock/http_lib_adapters/httpclient.rb +96 -0
- data/lib/webmock/http_lib_adapters/net_http.rb +4 -7
- data/lib/webmock/request.rb +29 -0
- data/lib/webmock/request_profile.rb +28 -15
- data/lib/webmock/request_registry.rb +12 -2
- data/lib/webmock/request_signature.rb +2 -1
- data/lib/webmock/request_stub.rb +1 -2
- data/lib/webmock/response.rb +18 -6
- data/lib/webmock/util/uri.rb +2 -2
- data/lib/webmock/webmock.rb +2 -2
- data/spec/httpclient_spec.rb +36 -0
- data/spec/httpclient_spec_helper.rb +57 -0
- data/spec/net_http_spec.rb +15 -24
- data/spec/net_http_spec_helper.rb +53 -0
- data/spec/request_execution_verifier_spec.rb +5 -5
- data/spec/request_profile_spec.rb +17 -10
- data/spec/request_registry_spec.rb +15 -15
- data/spec/request_signature_spec.rb +71 -56
- data/spec/request_stub_spec.rb +3 -3
- data/spec/response_spec.rb +7 -1
- data/spec/spec_helper.rb +5 -33
- data/spec/util/uri_spec.rb +67 -40
- data/spec/webmock_spec.rb +179 -164
- data/test/test_webmock.rb +25 -19
- data/webmock.gemspec +14 -3
- metadata +20 -2
@@ -0,0 +1,29 @@
|
|
1
|
+
module WebMock
|
2
|
+
|
3
|
+
class Request
|
4
|
+
attr_accessor :method, :uri, :body, :headers
|
5
|
+
|
6
|
+
def initialize(method, uri, options = {})
|
7
|
+
self.method = method
|
8
|
+
self.uri = uri.is_a?(Addressable::URI) ? uri : WebMock::Util::URI.normalize_uri(uri)
|
9
|
+
assign_options(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
string = "#{self.method.to_s.upcase} #{WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)}"
|
14
|
+
string << " with body '#{body.to_s}'" if body && body.to_s != ''
|
15
|
+
if headers && !headers.empty?
|
16
|
+
string << " with headers #{WebMock::Util::Headers.normalize_headers(headers).inspect.gsub("\"","'")}"
|
17
|
+
end
|
18
|
+
string
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def assign_options(options)
|
24
|
+
self.body = options[:body] if options.has_key?(:body)
|
25
|
+
self.headers = WebMock::Util::Headers.normalize_headers(options[:headers]) if options.has_key?(:headers)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -1,28 +1,41 @@
|
|
1
1
|
module WebMock
|
2
2
|
|
3
|
-
class RequestProfile <
|
4
|
-
|
5
|
-
def initialize(method, uri, body = nil, headers = nil)
|
6
|
-
super
|
7
|
-
self.uri = WebMock::Util::URI.normalize_uri(self.uri) unless self.uri.is_a?(Addressable::URI)
|
8
|
-
self.headers = WebMock::Util::Headers.normalize_headers(self.headers)
|
9
|
-
end
|
3
|
+
class RequestProfile < Request
|
10
4
|
|
11
5
|
def with(options)
|
12
|
-
|
13
|
-
self.headers = WebMock::Util::Headers.normalize_headers(options[:headers]) if options.has_key?(:headers)
|
6
|
+
assign_options(options)
|
14
7
|
self
|
15
8
|
end
|
9
|
+
|
10
|
+
def body=(body)
|
11
|
+
@body = Body.new(body)
|
12
|
+
end
|
13
|
+
|
14
|
+
class Body
|
15
|
+
|
16
|
+
attr_reader :data
|
17
|
+
|
18
|
+
def initialize(data)
|
19
|
+
@data = data
|
20
|
+
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
string << " with headers #{WebMock::Util::Headers.normalize_headers(headers).inspect.gsub("\"","'")}"
|
22
|
+
def ==(other)
|
23
|
+
other = Body.new(other) unless other.is_a?(Body)
|
24
|
+
other.is_a?(Body) &&
|
25
|
+
(other.is_empty? && self.is_empty? || other.data == self.data)
|
22
26
|
end
|
23
|
-
|
27
|
+
|
28
|
+
def is_empty?
|
29
|
+
@data.nil? || @data == ""
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
@data
|
34
|
+
end
|
35
|
+
|
24
36
|
end
|
25
37
|
|
26
38
|
end
|
27
39
|
|
40
|
+
|
28
41
|
end
|
@@ -25,9 +25,9 @@ module WebMock
|
|
25
25
|
|
26
26
|
def response_for_request(request_signature)
|
27
27
|
stub = request_stub_for(request_signature)
|
28
|
-
stub ? stub.response : nil
|
28
|
+
stub ? evaluate_response_for_request(stub.response, request_signature) : nil
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def times_executed(request_profile)
|
32
32
|
self.requested_signatures.hash.select { |request_signature, times_executed|
|
33
33
|
request_signature.match(request_profile)
|
@@ -42,5 +42,15 @@ module WebMock
|
|
42
42
|
}
|
43
43
|
end
|
44
44
|
|
45
|
+
def evaluate_response_for_request(response, request_signature)
|
46
|
+
evaluated_response = response.dup
|
47
|
+
[:body, :headers].each do |attribute|
|
48
|
+
if response.options[attribute].is_a?(Proc)
|
49
|
+
evaluated_response.options[attribute] = response.options[attribute].call(request_signature)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
evaluated_response
|
53
|
+
end
|
54
|
+
|
45
55
|
end
|
46
56
|
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
module WebMock
|
2
2
|
|
3
|
-
class RequestSignature <
|
3
|
+
class RequestSignature < Request
|
4
4
|
|
5
5
|
def match(request_profile)
|
6
|
+
raise ArgumentError unless request_profile.is_a?(RequestProfile)
|
6
7
|
match_method(request_profile) &&
|
7
8
|
match_body(request_profile) &&
|
8
9
|
match_headers(request_profile) &&
|
data/lib/webmock/request_stub.rb
CHANGED
data/lib/webmock/response.rb
CHANGED
@@ -1,24 +1,26 @@
|
|
1
1
|
module WebMock
|
2
2
|
class Response
|
3
|
+
attr_accessor :options
|
3
4
|
|
4
5
|
def initialize(options = {})
|
5
6
|
@options = options
|
7
|
+
@options[:headers] = Util::Headers.normalize_headers(@options[:headers]) unless @options[:headers].is_a?(Proc)
|
6
8
|
end
|
7
9
|
|
8
10
|
def headers
|
9
|
-
|
11
|
+
@options[:headers]
|
10
12
|
end
|
11
13
|
|
12
14
|
def body
|
13
15
|
return '' unless @options.has_key?(:body)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
case @options[:body]
|
17
|
+
when IO
|
18
|
+
@options[:body].read
|
19
|
+
when String
|
18
20
|
@options[:body]
|
19
21
|
end
|
20
22
|
end
|
21
|
-
|
23
|
+
|
22
24
|
def status
|
23
25
|
@options.has_key?(:status) ? @options[:status] : 200
|
24
26
|
end
|
@@ -27,5 +29,15 @@ module WebMock
|
|
27
29
|
raise @options[:exception].new('Exception from WebMock') if @options.has_key?(:exception)
|
28
30
|
end
|
29
31
|
|
32
|
+
def dup
|
33
|
+
dup_response = super
|
34
|
+
dup_response.options = options.dup
|
35
|
+
dup_response
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
options == other.options
|
40
|
+
end
|
41
|
+
|
30
42
|
end
|
31
43
|
end
|
data/lib/webmock/util/uri.rb
CHANGED
@@ -18,7 +18,7 @@ module WebMock
|
|
18
18
|
normalized_uri = Addressable::URI.heuristic_parse(uri)
|
19
19
|
normalized_uri.query_values = normalized_uri.query_values if normalized_uri.query_values
|
20
20
|
normalized_uri.normalize!
|
21
|
-
normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
|
21
|
+
normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
|
22
22
|
normalized_uri
|
23
23
|
end
|
24
24
|
|
@@ -34,7 +34,7 @@ module WebMock
|
|
34
34
|
uris = uris_with_scheme_and_without(uris)
|
35
35
|
end
|
36
36
|
|
37
|
-
if normalized_uri.path == '/'
|
37
|
+
if normalized_uri.path == '/'
|
38
38
|
uris = uris_with_trailing_slash_and_without(uris)
|
39
39
|
end
|
40
40
|
|
data/lib/webmock/webmock.rb
CHANGED
@@ -13,13 +13,13 @@ module WebMock
|
|
13
13
|
|
14
14
|
def assert_requested(method, uri, options = {})
|
15
15
|
expected_times_executed = options.delete(:times) || 1
|
16
|
-
request = RequestProfile.new(method, uri, options
|
16
|
+
request = RequestProfile.new(method, uri, options)
|
17
17
|
verifier = RequestExecutionVerifier.new(request, expected_times_executed)
|
18
18
|
assertion_failure(verifier.failure_message) unless verifier.matches?
|
19
19
|
end
|
20
20
|
|
21
21
|
def assert_not_requested(method, uri, options = {})
|
22
|
-
request = RequestProfile.new(method, uri, options
|
22
|
+
request = RequestProfile.new(method, uri, options)
|
23
23
|
verifier = RequestExecutionVerifier.new(request, options.delete(:times))
|
24
24
|
assertion_failure(verifier.negative_failure_message) unless verifier.does_not_match?
|
25
25
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'webmock_spec'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
require 'httpclient'
|
6
|
+
require 'httpclient_spec_helper'
|
7
|
+
|
8
|
+
describe "Webmock with HTTPClient" do
|
9
|
+
include HTTPClientSpecHelper
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
HTTPClientSpecHelper.async_mode = false
|
13
|
+
end
|
14
|
+
|
15
|
+
it_should_behave_like "WebMock"
|
16
|
+
|
17
|
+
it "should yield block on response if block provided" do
|
18
|
+
stub_http_request(:get, "www.example.com").to_return(:body => "abc")
|
19
|
+
response_body = ""
|
20
|
+
http_request(:get, "http://www.example.com/") do |body|
|
21
|
+
response_body = body
|
22
|
+
end
|
23
|
+
response_body.should == "abc"
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "async requests" do
|
27
|
+
|
28
|
+
before(:each) do
|
29
|
+
HTTPClientSpecHelper.async_mode = true
|
30
|
+
end
|
31
|
+
|
32
|
+
it_should_behave_like "WebMock"
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module HTTPClientSpecHelper
|
2
|
+
class << self
|
3
|
+
attr_accessor :async_mode
|
4
|
+
end
|
5
|
+
|
6
|
+
def http_request(method, uri, options = {}, &block)
|
7
|
+
uri = Addressable::URI.heuristic_parse(uri)
|
8
|
+
c = HTTPClient.new
|
9
|
+
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
10
|
+
c.set_basic_auth(nil, uri.user, uri.password) if uri.user
|
11
|
+
params = [method, "#{uri.omit(:userinfo, :query).normalize.to_s}",
|
12
|
+
uri.query_values, options[:body], options[:headers] || {}]
|
13
|
+
if HTTPClientSpecHelper.async_mode
|
14
|
+
connection = c.request_async(*params)
|
15
|
+
connection.join
|
16
|
+
response = connection.pop
|
17
|
+
else
|
18
|
+
response = c.request(*params, &block)
|
19
|
+
end
|
20
|
+
OpenStruct.new({
|
21
|
+
:body => HTTPClientSpecHelper.async_mode ? response.content.read : response.content,
|
22
|
+
:headers => Hash[response.header.all],
|
23
|
+
:status => response.code.to_s })
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_expectations_for_real_request(options = {})
|
27
|
+
socket = mock("TCPSocket")
|
28
|
+
TCPSocket.should_receive(:new).
|
29
|
+
with(options[:host], options[:port]).at_least(:once).and_return(socket)
|
30
|
+
|
31
|
+
socket.stub!(:closed?).and_return(false)
|
32
|
+
socket.stub!(:close).and_return(true)
|
33
|
+
|
34
|
+
request_parts = ["#{options[:method]} #{options[:path]} HTTP/1.1", "Host: #{options[:host]}"]
|
35
|
+
|
36
|
+
if options[:port] == 443
|
37
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).
|
38
|
+
with(socket, instance_of(OpenSSL::SSL::SSLContext)).
|
39
|
+
at_least(:once).and_return(socket = mock("SSLSocket"))
|
40
|
+
socket.should_receive(:connect).at_least(:once).with()
|
41
|
+
socket.should_receive(:peer_cert).and_return(mock('peer cert', :extensions => []))
|
42
|
+
socket.should_receive(:write).with(/#{request_parts[0]}.*#{request_parts[1]}.*/m).and_return(100)
|
43
|
+
else
|
44
|
+
socket.should_receive(:<<).with(/#{request_parts[0]}.*#{request_parts[1]}.*/m).and_return(100)
|
45
|
+
end
|
46
|
+
|
47
|
+
socket.stub!(:sync=).with(true)
|
48
|
+
|
49
|
+
socket.should_receive(:gets).with("\n").once.and_return("HTTP/1.1 #{options[:response_code]} #{options[:response_message]}\nContent-Length: #{options[:response_body].length}\n\n#{options[:response_body]}")
|
50
|
+
|
51
|
+
socket.stub!(:eof?).and_return(true)
|
52
|
+
socket.stub!(:close).and_return(true)
|
53
|
+
|
54
|
+
socket.should_receive(:readpartial).any_number_of_times.and_raise(EOFError)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/spec/net_http_spec.rb
CHANGED
@@ -1,34 +1,25 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
require 'webmock_spec'
|
3
3
|
require 'ostruct'
|
4
|
+
require 'net_http_spec_helper'
|
4
5
|
|
5
|
-
|
6
|
-
begin
|
7
|
-
uri = URI.parse(uri)
|
8
|
-
rescue
|
9
|
-
uri = Addressable::URI.heuristic_parse(uri)
|
10
|
-
end
|
11
|
-
response = nil
|
12
|
-
clazz = Net::HTTP.const_get("#{method.to_s.capitalize}")
|
13
|
-
req = clazz.new("#{uri.path}#{uri.query ? '?' : ''}#{uri.query}", options[:headers])
|
14
|
-
req.basic_auth uri.user, uri.password if uri.user
|
15
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
16
|
-
http.use_ssl = true if uri.scheme == "https"
|
17
|
-
response = http.start {|http|
|
18
|
-
http.request(req, options[:body])
|
19
|
-
}
|
20
|
-
OpenStruct.new({
|
21
|
-
:body => response.body,
|
22
|
-
:headers => response,
|
23
|
-
:status => response.code })
|
24
|
-
end
|
6
|
+
include NetHTTPSpecHelper
|
25
7
|
|
26
8
|
describe "Webmock with Net:HTTP" do
|
27
|
-
|
9
|
+
|
28
10
|
it_should_behave_like "WebMock"
|
29
|
-
|
11
|
+
|
30
12
|
it "should work with block provided" do
|
31
|
-
stub_http_request(:get, "www.
|
32
|
-
Net::HTTP.start("www.
|
13
|
+
stub_http_request(:get, "www.example.com").to_return(:body => "abc"*100000)
|
14
|
+
Net::HTTP.start("www.example.com") { |query| query.get("/") }.body.should == "abc"*100000
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should yield block on response" do
|
18
|
+
stub_http_request(:get, "www.example.com").to_return(:body => "abc")
|
19
|
+
response_body = ""
|
20
|
+
http_request(:get, "http://www.example.com/") do |response|
|
21
|
+
response_body = response.body
|
22
|
+
end
|
23
|
+
response_body.should == "abc"
|
33
24
|
end
|
34
25
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module NetHTTPSpecHelper
|
2
|
+
def http_request(method, uri, options = {}, &block)
|
3
|
+
begin
|
4
|
+
uri = URI.parse(uri)
|
5
|
+
rescue
|
6
|
+
uri = Addressable::URI.heuristic_parse(uri)
|
7
|
+
end
|
8
|
+
response = nil
|
9
|
+
clazz = Net::HTTP.const_get("#{method.to_s.capitalize}")
|
10
|
+
req = clazz.new("#{uri.path}#{uri.query ? '?' : ''}#{uri.query}", options[:headers])
|
11
|
+
|
12
|
+
req.basic_auth uri.user, uri.password if uri.user
|
13
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
14
|
+
http.use_ssl = true if uri.scheme == "https"
|
15
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
16
|
+
response = http.start {|http|
|
17
|
+
http.request(req, options[:body], &block)
|
18
|
+
}
|
19
|
+
OpenStruct.new({
|
20
|
+
:body => response.body,
|
21
|
+
:headers => response,
|
22
|
+
:status => response.code })
|
23
|
+
end
|
24
|
+
|
25
|
+
# Sets several expectations that a real HTTP request makes it
|
26
|
+
# past WebMock to the socket layer. You can use this when you need to check
|
27
|
+
# that a request isn't handled by WebMock
|
28
|
+
#This solution is copied from FakeWeb project
|
29
|
+
def setup_expectations_for_real_request(options = {})
|
30
|
+
# Socket handling
|
31
|
+
if options[:port] == 443
|
32
|
+
socket = mock("SSLSocket")
|
33
|
+
OpenSSL::SSL::SSLSocket.should_receive(:===).with(socket).at_least(:once).and_return(true)
|
34
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).with(socket, instance_of(OpenSSL::SSL::SSLContext)).at_least(:once).and_return(socket)
|
35
|
+
socket.stub!(:sync_close=).and_return(true)
|
36
|
+
socket.should_receive(:connect).at_least(:once).with()
|
37
|
+
else
|
38
|
+
socket = mock("TCPSocket")
|
39
|
+
Socket.should_receive(:===).with(socket).at_least(:once).and_return(true)
|
40
|
+
end
|
41
|
+
|
42
|
+
TCPSocket.should_receive(:open).with(options[:host], options[:port]).at_least(:once).and_return(socket)
|
43
|
+
socket.stub!(:closed?).and_return(false)
|
44
|
+
socket.stub!(:close).and_return(true)
|
45
|
+
|
46
|
+
# Request/response handling
|
47
|
+
request_parts = ["#{options[:method]} #{options[:path]} HTTP/1.1", "Host: #{options[:host]}"]
|
48
|
+
socket.should_receive(:write).with(/#{request_parts[0]}.*#{request_parts[1]}.*/m).and_return(100)
|
49
|
+
|
50
|
+
socket.should_receive(:sysread).once.and_return("HTTP/1.1 #{options[:response_code]} #{options[:response_message]}\nContent-Length: #{options[:response_body].length}\n\n#{options[:response_body]}")
|
51
|
+
socket.should_receive(:sysread).any_number_of_times.and_raise(EOFError)
|
52
|
+
end
|
53
|
+
end
|
@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
3
|
describe RequestExecutionVerifier do
|
4
4
|
before(:each) do
|
5
5
|
@verifier = RequestExecutionVerifier.new
|
6
|
-
@request_profile = mock(RequestProfile, :to_s => "www.
|
6
|
+
@request_profile = mock(RequestProfile, :to_s => "www.example.com")
|
7
7
|
@verifier.request_profile = @request_profile
|
8
8
|
end
|
9
9
|
|
@@ -13,13 +13,13 @@ describe RequestExecutionVerifier do
|
|
13
13
|
it "should report failure message" do
|
14
14
|
@verifier.times_executed = 0
|
15
15
|
@verifier.expected_times_executed = 2
|
16
|
-
@verifier.failure_message.should == "The request www.
|
16
|
+
@verifier.failure_message.should == "The request www.example.com was expected to execute 2 times but it executed 0 times"
|
17
17
|
end
|
18
18
|
|
19
19
|
it "should report failure message correctly when executed times is one" do
|
20
20
|
@verifier.times_executed = 1
|
21
21
|
@verifier.expected_times_executed = 1
|
22
|
-
@verifier.failure_message.should == "The request www.
|
22
|
+
@verifier.failure_message.should == "The request www.example.com was expected to execute 1 time but it executed 1 time"
|
23
23
|
end
|
24
24
|
|
25
25
|
end
|
@@ -29,12 +29,12 @@ describe RequestExecutionVerifier do
|
|
29
29
|
it "should report failure message if it executed number of times specified" do
|
30
30
|
@verifier.times_executed = 2
|
31
31
|
@verifier.expected_times_executed = 2
|
32
|
-
@verifier.negative_failure_message.should == "The request www.
|
32
|
+
@verifier.negative_failure_message.should == "The request www.example.com was not expected to execute 2 times but it executed 2 times"
|
33
33
|
end
|
34
34
|
|
35
35
|
it "should report failure message when not expected request but it executed" do
|
36
36
|
@verifier.times_executed = 1
|
37
|
-
@verifier.negative_failure_message.should == "The request www.
|
37
|
+
@verifier.negative_failure_message.should == "The request www.example.com was expected to execute 0 times but it executed 1 time"
|
38
38
|
end
|
39
39
|
|
40
40
|
end
|