7d7e378436898f7154d84f62ed74c1567f574e23
1 require 'active_resource/connection'
4 class InvalidRequestError
< StandardError
; end #:nodoc:
6 # One thing that has always been a pain with remote web services is testing. The HttpMock
7 # class makes it easy to test your Active Resource models by creating a set of mock responses to specific
10 # To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to
11 # method with an attached block. The block declares a set of URIs with expected input, and the output
12 # each request should return. The passed in block has any number of entries in the following generalized
15 # mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
17 # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or
19 # * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
21 # * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
22 # hash format, such as <tt>{ "Content-Type" => "application/xml" }</tt>. This mock will only trigger
23 # if your tests sends a request with identical headers.
24 # * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
26 # * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
27 # * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
28 # <tt>request_headers</tt> listed above.
30 # In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
31 # +path+ and <tt>request_headers</tt>. If no match is found an InvalidRequestError exception
32 # will be raised letting you know you need to create a new mock for that request.
36 # @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
37 # ActiveResource::HttpMock.respond_to do |mock|
38 # mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
39 # mock.get "/people/1.xml", {}, @matz
40 # mock.put "/people/1.xml", {}, nil, 204
41 # mock.delete "/people/1.xml", {}, nil, 200
46 # person = Person.find(1)
47 # assert_equal "Matz", person.name
51 class Responder
#:nodoc:
52 def initialize(responses
)
53 @responses = responses
56 for method
in [ :post, :put, :get, :delete, :head ]
57 # def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
58 # @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
60 module_eval
<<-EOE, __FILE__, __LINE__
61 def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
62 @responses << [Request.new(:#{method}, path, nil, request_headers), Response.new(body || "", status, response_headers)]
70 # Returns an array of all request objects that have been sent to the mock. You can use this to check
71 # if your model actually sent an HTTP request.
75 # @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
76 # ActiveResource::HttpMock.respond_to do |mock|
77 # mock.get "/people/1.xml", {}, @matz
81 # def test_should_request_remote_service
82 # person = Person.find(1) # Call the remote service
84 # # This request object has the same HTTP method and path as declared by the mock
85 # expected_request = ActiveResource::Request.new(:get, "/people/1.xml")
87 # # Assert that the mock received, and responded to, the expected request from the model
88 # assert ActiveResource::HttpMock.requests.include?(expected_request)
94 # Returns the list of requests and their mocked responses. Look up a
95 # response for a request using responses.assoc(request).
100 # Accepts a block which declares a set of requests and responses for the HttpMock to respond to. See the main
101 # ActiveResource::HttpMock description for a more detailed explanation.
102 def respond_to(pairs
= {}) #:yields: mock
104 responses
.concat pairs
.to_a
106 yield Responder
.new(responses
)
108 Responder
.new(responses
)
112 # Deletes all logged requests and responses.
120 { true => %w(post put
),
121 false => %w(get delete head
) }.each
do |has_body
, methods
|
122 methods
.each
do |method
|
123 # def post(path, body, headers)
124 # request = ActiveResource::Request.new(:post, path, body, headers)
125 # self.class.requests << request
126 # self.class.responses.assoc(request).try(:second) || raise(InvalidRequestError.new("No response recorded for #{request}"))
128 module_eval
<<-EOE, __FILE__, __LINE__
129 def #{method}(path, #{'body, ' if has_body}headers)
130 request = ActiveResource::Request.new(:#{method}, path, #{has_body ? 'body, ' : 'nil, '}headers)
131 self.class.requests << request
132 self.class.responses.assoc(request).try(:second) || raise(InvalidRequestError.new("No response recorded for \#{request}"))
138 def initialize(site
) #:nodoc:
144 attr_accessor
:path, :method, :body, :headers
146 def initialize(method
, path
, body
= nil, headers
= {})
147 @method, @path, @body, @headers = method
, path
, body
, headers
.merge(ActiveResource
::Connection::HTTP_FORMAT_HEADER_NAMES[method
] => 'application/xml')
151 path
== req
.path
&& method
== req
.method
&& headers
== req
.headers
155 "<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
160 attr_accessor
:body, :message, :code, :headers
162 def initialize(body
, message
= 200, headers
= {})
163 @body, @message, @headers = body
, message
.to_s
, headers
164 @code = @message[0,3].to_i
166 resp_cls
= Net
::HTTPResponse::CODE_TO_OBJ[@code.to_s
]
167 if resp_cls
&& !resp_cls
.body_permitted
?
172 self['Content-Length'] = "0"
174 self['Content-Length'] = body
.size
.to_s
179 (200..299).include?(code
)
191 if (other
.is_a
?(Response
))
192 other
.body
== body
&& other
.message
== message
&& other
.headers
== headers
203 @http ||= HttpMock
.new(@site)