Permalink
Browse files

Added support for matching partial query params with "hash_including"

  • Loading branch information...
1 parent 1e658f8 commit 2d03aca4acb3a65e976250c538393f373ffbdb9d @bblimke committed Feb 5, 2012
View
@@ -172,6 +172,12 @@ You can also use WebMock outside a test framework:
RestClient.get("http://www.example.com/?a[]=b&a[]=c") # ===> Success
+### Matching partial query params using hash
+
+ stub_http_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
+
### Stubbing with custom response
stub_request(:any, "www.example.com").to_return(:body => "abc", :status => 200, :headers => { 'Content-Length' => 3 } )
@@ -405,6 +411,8 @@ This forces WebMock Net::HTTP adapter to always connect on `Net::HTTP.start`.
WebMock.should have_requested(:get, "www.example.com").with(:query => {"a" => ["b", "c"]})
+ WebMock.should have_requested(:get, "www.example.com").with(:query => hash_including({"a" => ["b", "c"]}))
+
WebMock.should have_requested(:get, "www.example.com").
with(:body => {"a" => ["b", "c"]}, :headers => {'Content-Type' => 'application/json'})
@@ -420,6 +428,8 @@ This forces WebMock Net::HTTP adapter to always connect on `Net::HTTP.start`.
a_request(:get, "www.example.com").with(:query => {"a" => ["b", "c"]}).should have_been_made
+ a_request(:get, "www.example.com").with(:query => hash_including({"a" => ["b", "c"]})).should have_been_made
+
a_request(:post, "www.example.com").
with(:body => {"a" => ["b", "c"]}, :headers => {'Content-Type' => 'application/json'}).should have_been_made
View
@@ -24,6 +24,8 @@
require 'webmock/util/hash_keys_stringifier'
require 'webmock/util/json'
+require 'webmock/matchers/hash_including_matcher'
+
require 'webmock/request_pattern'
require 'webmock/request_signature'
require 'webmock/responses_sequence'
View
@@ -32,6 +32,14 @@ def assert_not_requested(*args, &block)
assert_request_not_requested(*args, &block)
end
+ def hash_including(expected)
+ if defined?(RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher)
+ RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher.new(expected)
+ else
+ WebMock::Matchers::HashIncludingMatcher.new(expected)
+ end
+ end
+
private
def convert_uri_method_and_options_to_request_and_options(*args, &block)
@@ -0,0 +1,25 @@
+module WebMock
+ module Matchers
+ #this is a based on RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher
+ #https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
+ class HashIncludingMatcher
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def ==(actual)
+ @expected.all? {|k,v| actual.has_key?(k) && v == actual[k]}
+ rescue NoMethodError
+ false
+ end
+
+ def inspect
+ "hash_including(#{@expected.inspect})"
+ end
+
+ def self.from_rspec_matcher(matcher)
+ new(matcher.instance_variable_get(:@expected))
+ end
+ end
+ end
+end
@@ -40,19 +40,19 @@ def to_s
private
- def assign_options(options)
- @body_pattern = BodyPattern.new(options[:body]) if options.has_key?(:body)
- @headers_pattern = HeadersPattern.new(options[:headers]) if options.has_key?(:headers)
- @uri_pattern.add_query_params(options[:query]) if options.has_key?(:query)
- end
+ def assign_options(options)
+ @body_pattern = BodyPattern.new(options[:body]) if options.has_key?(:body)
+ @headers_pattern = HeadersPattern.new(options[:headers]) if options.has_key?(:headers)
+ @uri_pattern.add_query_params(options[:query]) if options.has_key?(:query)
+ end
- def create_uri_pattern(uri)
- if uri.is_a?(Regexp)
- URIRegexpPattern.new(uri)
- else
- URIStringPattern.new(uri)
+ def create_uri_pattern(uri)
+ if uri.is_a?(Regexp)
+ URIRegexpPattern.new(uri)
+ else
+ URIStringPattern.new(uri)
+ end
end
- end
end
@@ -76,6 +76,25 @@ class URIPattern
def initialize(pattern)
@pattern = pattern.is_a?(Addressable::URI) ? pattern : WebMock::Util::URI.normalize_uri(pattern)
end
+
+ def add_query_params(query_params)
+ @query_params = case query_params
+ when Hash
+ query_params
+ when WebMock::Matchers::HashIncludingMatcher
+ query_params
+ when RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher
+ WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
+ else
+ Addressable::URI.parse('?' + query_params).query_values
+ end
+ end
+
+ def to_s
+ str = @pattern.inspect
+ str += " with query params #{@query_params.inspect}" if @query_params
+ str
+ end
end
class URIRegexpPattern < URIPattern
@@ -94,33 +113,34 @@ def to_s
str += " with query params #{@query_params.inspect}" if @query_params
str
end
-
- def add_query_params(query_params)
- @query_params = query_params.is_a?(Hash) ? query_params : Addressable::URI.parse('?' + query_params).query_values
- end
-
end
class URIStringPattern < URIPattern
def matches?(uri)
if @pattern.is_a?(Addressable::URI)
- uri === @pattern
+ if @query_params
+ uri.omit(:query) === @pattern && (@query_params.nil? || @query_params == uri.query_values)
+ else
+ uri === @pattern
+ end
else
false
end
end
def add_query_params(query_params)
- if !query_params.is_a?(Hash)
- query_params = Addressable::URI.parse('?' + query_params).query_values
+ super
+ if @query_params.is_a?(Hash) || @query_params.is_a?(String)
+ @pattern.query_values = (@pattern.query_values || {}).merge(@query_params)
+ @query_params = nil
end
- @pattern.query_values = (@pattern.query_values || {}).merge(query_params)
end
def to_s
- WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
+ str = WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
+ str += " with query params #{@query_params.inspect}" if @query_params
+ str
end
-
end
@@ -171,50 +191,50 @@ def to_s
private
- # 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'}}
- #
- # @param [Hash] query_parameters typically the result of parsing
- # JSON, XML or URL encoded parameters.
- #
- # @param [Hash] pattern which contains keys with a string, hash or
- # regular expression value to use for comparison.
- #
- # @return [Boolean] true if the paramaters match the comparison
- # hash, false if not.
- def matching_hashes?(query_parameters, pattern)
- return false unless query_parameters.size == pattern.size
- query_parameters.each do |key, actual|
- expected = pattern[key]
-
- if actual.is_a?(Hash) && expected.is_a?(Hash)
- return false unless matching_hashes?(actual, expected)
- else
- return false unless expected === actual
+ # 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'}}
+ #
+ # @param [Hash] query_parameters typically the result of parsing
+ # JSON, XML or URL encoded parameters.
+ #
+ # @param [Hash] pattern which contains keys with a string, hash or
+ # regular expression value to use for comparison.
+ #
+ # @return [Boolean] true if the paramaters match the comparison
+ # hash, false if not.
+ def matching_hashes?(query_parameters, pattern)
+ return false unless query_parameters.size == pattern.size
+ query_parameters.each do |key, actual|
+ expected = pattern[key]
+
+ if actual.is_a?(Hash) && expected.is_a?(Hash)
+ return false unless matching_hashes?(actual, expected)
+ else
+ return false unless expected === actual
+ end
end
+ true
end
- true
- end
- def empty_string?(string)
- string.nil? || string == ""
- end
+ def empty_string?(string)
+ string.nil? || string == ""
+ end
- def normalize_hash(hash)
- Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(hash).sort]
- end
+ def normalize_hash(hash)
+ Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(hash).sort]
+ end
end
@@ -241,9 +261,9 @@ def to_s
private
- def empty_headers?(headers)
- headers.nil? || headers == {}
- end
+ def empty_headers?(headers)
+ headers.nil? || headers == {}
+ end
end
end
@@ -150,6 +150,13 @@
a_request(:get, "www.example.com/?x=3").with(:query => {"a" => ["b", "c"]}).should have_been_made
}.should_not raise_error
end
+
+ it "should satisfy expectation if the request was executed with only part query params declared as a hash in a query option" do
+ lambda {
+ http_request(:get, "http://www.example.com/?a[]=b&a[]=c&b=1")
+ a_request(:get, "www.example.com").with(:query => hash_including({"a" => ["b", "c"]})).should have_been_made
+ }.should_not raise_error
+ end
end
it "should fail if request was made more times than expected" do
@@ -32,6 +32,11 @@
stub_request(:get, "www.example.com/?x=3").with(:query => {"a" => ["b", "c"]}).to_return(:body => "abc")
http_request(:get, "http://www.example.com/?x=3&a[]=b&a[]=c").body.should == "abc"
end
+
+ it "should return stubbed response when stub expects only part of query params" do
+ stub_request(:get, "www.example.com").with(:query => hash_including({"a" => ["b", "c"]})).to_return(:body => "abc")
+ http_request(:get, "http://www.example.com/?a[]=b&a[]=c&b=1").body.should == "abc"
+ end
end
describe "based on method" do
Oops, something went wrong.

0 comments on commit 2d03aca

Please sign in to comment.