hyperspec 0.0.1
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.
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/README.md +118 -0
- data/Rakefile +1 -0
- data/examples/service/example.rb +23 -0
- data/examples/service/example.ru +5 -0
- data/examples/spec/example_spec.rb +52 -0
- data/hyperspec.gemspec +44 -0
- data/lib/hyperspec.rb +193 -0
- data/lib/hyperspec/version.rb +6 -0
- data/spec/fixtures/vcr_cassettes/localhost.yml +94 -0
- data/spec/hyperspec_spec.rb +202 -0
- data/spec/support/meta_spec.rb +14 -0
- data/spec/support/vcr.rb +1 -0
- metadata +136 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# This is Work in Progress
|
2
|
+
|
3
|
+
__ __ ______
|
4
|
+
/ / / /_ _________________/ ____/________________
|
5
|
+
/ /_/ / / / / __ \ _ \_ __/____ \/ __ \ _ \ ___/
|
6
|
+
/ __ / /_/ / /_/ / __/ / ____/ / /_/ / __/ /__
|
7
|
+
/_/ /_/\__, / ____/\___/_/ /_____/ ____/\___/\___/
|
8
|
+
/____/_/ /_/
|
9
|
+
|
10
|
+
A full stack testing framework for HTTP APIs.
|
11
|
+
|
12
|
+
By extending `minitest/spec` HyperSpec provides a Ruby DSL for testing
|
13
|
+
HTTP APIs from the outside.
|
14
|
+
|
15
|
+
## Example
|
16
|
+
|
17
|
+
service "http://localhost:4567" do
|
18
|
+
def response_json
|
19
|
+
JSON.parse(response.body)
|
20
|
+
end
|
21
|
+
|
22
|
+
resource "/lolz" do
|
23
|
+
get do
|
24
|
+
it { responds_with.status :ok }
|
25
|
+
it { response_json['lolz'].must_be_instance_of Array }
|
26
|
+
|
27
|
+
with_query("q=monorail") do
|
28
|
+
it "only lists lolz that match the query" do
|
29
|
+
response_json['lolz'].wont_be_empty
|
30
|
+
response_json['lolz'].each do |lol|
|
31
|
+
lol['title'].must_match /monorail/
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
post do
|
38
|
+
describe "without request body" do
|
39
|
+
it { responds_with.status :unprocessable_entity }
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "with request body" do
|
43
|
+
with_headers({ 'Content-Type' => 'application/json' }) do
|
44
|
+
with_request_body({ "title" => "Roflcopter!" }.to_json) do
|
45
|
+
it { responds_with.status :created }
|
46
|
+
it { response_json['lol']['title'].must_equal 'Roflcopter!' }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
## Concepts
|
55
|
+
|
56
|
+
### `service`
|
57
|
+
|
58
|
+
Sets the `BASE_URI` of the API.
|
59
|
+
|
60
|
+
### `resource`
|
61
|
+
|
62
|
+
Sets the `URI` under test. Absolute or relative to the current scope.
|
63
|
+
|
64
|
+
### `get`, `head`, `post`, `put` and `delete`
|
65
|
+
|
66
|
+
Selects the HTTP method for the request.
|
67
|
+
|
68
|
+
### `with_query_string`
|
69
|
+
|
70
|
+
Sets the query parameters used for a request. Merges with previously set parameters.
|
71
|
+
|
72
|
+
### `with_headers`
|
73
|
+
|
74
|
+
Sets the headers used for a request. Merges with previously set headers.
|
75
|
+
|
76
|
+
### `with_body`
|
77
|
+
|
78
|
+
Sets the body used for a request. Overrides previously set parameters.
|
79
|
+
|
80
|
+
### `response`
|
81
|
+
|
82
|
+
An object for accessing properties of the "raw" response.
|
83
|
+
|
84
|
+
### `responds_with`
|
85
|
+
|
86
|
+
Issues the request and returns an object that has the following convenience matchers:
|
87
|
+
|
88
|
+
#### `status`
|
89
|
+
|
90
|
+
Allows for comparing against named status code symbols.
|
91
|
+
|
92
|
+
#### `content_type`
|
93
|
+
|
94
|
+
#### `content_charset`
|
95
|
+
|
96
|
+
## Upcoming features
|
97
|
+
|
98
|
+
### Representations
|
99
|
+
|
100
|
+
- DSL for matching representations.
|
101
|
+
|
102
|
+
## Concerns
|
103
|
+
|
104
|
+
- Efficient ways of building up and verifying precondition state.
|
105
|
+
- Verify an eventual consistent state.
|
106
|
+
- Allowing whitebox testing by "wormholing" into the application(s).
|
107
|
+
|
108
|
+
## Acknowledgments
|
109
|
+
|
110
|
+
Thanks to Daniel Bornkessel for inspiring me to do README driven development.
|
111
|
+
|
112
|
+
## History
|
113
|
+
|
114
|
+
### 2012 02 07
|
115
|
+
|
116
|
+
#### 0.0.1
|
117
|
+
|
118
|
+
- Initial release
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
get "/lolz" do
|
5
|
+
title = params[:q]
|
6
|
+
%^{"lolz": [ { "title": "#{title}" } ]}^
|
7
|
+
end
|
8
|
+
|
9
|
+
post "/lolz" do
|
10
|
+
if request.env['CONTENT_TYPE'] == "application/json"
|
11
|
+
params = JSON.parse(request.env['rack.input'].read)
|
12
|
+
|
13
|
+
if params.nil? || params.empty?
|
14
|
+
status 422
|
15
|
+
%^{"error": "An error occurred!"}^
|
16
|
+
else
|
17
|
+
status 201
|
18
|
+
%^{"lol": {"title": "Roflcopter!"}}^
|
19
|
+
end
|
20
|
+
else
|
21
|
+
status 422
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'hyperspec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
# Run `shotgun -p 4567 example.ru` from `examples/service/`.
|
7
|
+
|
8
|
+
service "http://localhost:4567" do
|
9
|
+
def response_json
|
10
|
+
JSON.parse(response.body)
|
11
|
+
end
|
12
|
+
|
13
|
+
resource "/lolz" do
|
14
|
+
get do
|
15
|
+
it { responds_with.status :ok }
|
16
|
+
it { response_json['lolz'].must_be_instance_of Array }
|
17
|
+
|
18
|
+
with_query("q=monorail") do
|
19
|
+
it "only lists lolz that match the query" do
|
20
|
+
response_json['lolz'].wont_be_empty
|
21
|
+
response_json['lolz'].each do |lol|
|
22
|
+
lol['title'].must_match /monorail/
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
with_query("q=looong") do
|
28
|
+
it "only lists lolz that match the query" do
|
29
|
+
response_json['lolz'].wont_be_empty
|
30
|
+
response_json['lolz'].each do |lol|
|
31
|
+
lol['title'].must_match /looong/
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
post do
|
38
|
+
describe "without request body" do
|
39
|
+
it { responds_with.status :unprocessable_entity }
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "with request body" do
|
43
|
+
with_headers({ 'Content-Type' => 'application/json' }) do
|
44
|
+
with_request_body({ "title" => "Roflcopter!" }.to_json) do
|
45
|
+
it { responds_with.status :created }
|
46
|
+
it { response_json['lol']['title'].must_equal 'Roflcopter!' }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/hyperspec.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "hyperspec/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "hyperspec"
|
7
|
+
s.version = HyperSpec::VERSION
|
8
|
+
s.authors = [ "Hannes Tydén" ]
|
9
|
+
s.email = [ "hannes@soundcloud.com" ]
|
10
|
+
s.homepage = "http://github.com/hannestyden/hyperspec"
|
11
|
+
s.summary = "Full stack HTTP API testing DSL."
|
12
|
+
s.description = <<-DESCRIPTION
|
13
|
+
By extending minitest/spec HyperSpec provides a Ruby DSL for testing HTTP APIs from the "outside".
|
14
|
+
DESCRIPTION
|
15
|
+
|
16
|
+
# Required for validation.
|
17
|
+
s.rubyforge_project = "hyperspec"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = [ "lib" ]
|
23
|
+
|
24
|
+
dependencies =
|
25
|
+
[
|
26
|
+
[ "minitest", "~> 2.11" ],
|
27
|
+
]
|
28
|
+
|
29
|
+
developement_dependencies =
|
30
|
+
[
|
31
|
+
[ "vcr", "~> 1.6" ],
|
32
|
+
[ "webmock" ],
|
33
|
+
]
|
34
|
+
|
35
|
+
runtime_dependencies = []
|
36
|
+
|
37
|
+
(dependencies + developement_dependencies).each do |dependency|
|
38
|
+
s.add_development_dependency *dependency
|
39
|
+
end
|
40
|
+
|
41
|
+
(dependencies + developement_dependencies).each do |dependency|
|
42
|
+
s.add_runtime_dependency *dependency
|
43
|
+
end
|
44
|
+
end
|
data/lib/hyperspec.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
gem 'minitest' # Ensure gem is used over built in.
|
5
|
+
require 'minitest/spec'
|
6
|
+
|
7
|
+
require 'hyperspec/version'
|
8
|
+
|
9
|
+
module HyperSpec
|
10
|
+
module ObjectExtensions
|
11
|
+
private
|
12
|
+
def service(desc, additional_desc = nil, &block)
|
13
|
+
cls = describe(desc, additional_desc, &block)
|
14
|
+
cls.send(:define_method, :base_uri) { URI.parse(desc) }
|
15
|
+
cls.send(:define_method, :headers) { {} }
|
16
|
+
cls.send(:define_method, :request_body) { "" }
|
17
|
+
cls
|
18
|
+
end
|
19
|
+
|
20
|
+
def resource(path, additional_desc = nil, &block)
|
21
|
+
cls = describe(desc, additional_desc, &block)
|
22
|
+
|
23
|
+
cls.send(:define_method, :base_uri) do
|
24
|
+
s = super()
|
25
|
+
s.path = [ s.path, path ].reject(&:empty?).join("/")
|
26
|
+
s
|
27
|
+
end
|
28
|
+
cls
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_headers(hash, additional_desc = nil, &block)
|
32
|
+
cls = describe("with headers", additional_desc, &block)
|
33
|
+
cls.send(:define_method, :headers) { super().merge(hash) }
|
34
|
+
cls
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_query(string, additional_desc = nil, &block)
|
38
|
+
cls = describe("with query", additional_desc, &block)
|
39
|
+
cls.send(:define_method, :base_uri) do
|
40
|
+
s = super()
|
41
|
+
s.query = [ s.query.to_s, string ].reject(&:empty?).join("&")
|
42
|
+
s
|
43
|
+
end
|
44
|
+
cls
|
45
|
+
end
|
46
|
+
|
47
|
+
def with_request_body(string, additional_desc = nil, &block)
|
48
|
+
cls = describe("with request body", additional_desc, &block)
|
49
|
+
cls.send(:define_method, :request_body) do
|
50
|
+
string
|
51
|
+
end
|
52
|
+
cls
|
53
|
+
end
|
54
|
+
|
55
|
+
# HTTP method selection
|
56
|
+
#
|
57
|
+
def get(additional_desc = nil, &block)
|
58
|
+
cls = describe('GET', additional_desc, &block)
|
59
|
+
cls.instance_eval { |_| def request_type; :get; end }
|
60
|
+
cls
|
61
|
+
end
|
62
|
+
|
63
|
+
def head(additional_desc = nil, &block)
|
64
|
+
cls = describe('HEAD', additional_desc, &block)
|
65
|
+
cls.instance_eval { |_| def request_type; :head; end }
|
66
|
+
cls
|
67
|
+
end
|
68
|
+
def post(additional_desc = nil, &block)
|
69
|
+
cls = describe('POST', additional_desc, &block)
|
70
|
+
cls.instance_eval { |_| def request_type; :post; end }
|
71
|
+
cls
|
72
|
+
end
|
73
|
+
def put(additional_desc = nil, &block)
|
74
|
+
cls = describe('PUT', additional_desc, &block)
|
75
|
+
cls.instance_eval { |_| def request_type; :put; end }
|
76
|
+
cls
|
77
|
+
end
|
78
|
+
def delete(additional_desc = nil, &block)
|
79
|
+
cls = describe('DELETE', additional_desc, &block)
|
80
|
+
cls.instance_eval { |_| def request_type; :delete; end }
|
81
|
+
cls
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module MiniTest
|
86
|
+
module SpecExtensions
|
87
|
+
def response
|
88
|
+
do_request
|
89
|
+
end
|
90
|
+
|
91
|
+
def request_type
|
92
|
+
self.class.request_type
|
93
|
+
end
|
94
|
+
|
95
|
+
def responds_with
|
96
|
+
Have.new(response)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def do_request
|
101
|
+
klass = eval("Net::HTTP::#{request_type.to_s.gsub(/^\w/) { |c| c.upcase }}")
|
102
|
+
@do_request ||=
|
103
|
+
request_response(klass, base_uri, headers, request_body)
|
104
|
+
end
|
105
|
+
|
106
|
+
def request_response(klass, uri, headers, body = '')
|
107
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
108
|
+
http.use_ssl = URI::HTTPS === uri
|
109
|
+
|
110
|
+
# NO DON'T DO IT!
|
111
|
+
# http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
112
|
+
# YOU DIDN'T DO IT, DID YOU?
|
113
|
+
|
114
|
+
resp =
|
115
|
+
http.start do
|
116
|
+
request_uri = [ uri.path, uri.query ].join("?")
|
117
|
+
req = klass.new(request_uri)
|
118
|
+
headers.inject(req) { |m, (k, v)| m[k] = v; m }
|
119
|
+
req.body = body if body
|
120
|
+
if headers['Content-Type']
|
121
|
+
req.content_type = headers['Content-Type']
|
122
|
+
end
|
123
|
+
http.request(req)
|
124
|
+
end
|
125
|
+
Response.from_net_http_response(resp)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
Response = Struct.new(:status_code, :headers, :body) do
|
131
|
+
STATI = {
|
132
|
+
# 2xx
|
133
|
+
200 => :ok,
|
134
|
+
201 => :created,
|
135
|
+
|
136
|
+
# 4xx
|
137
|
+
401 => :unauthorized,
|
138
|
+
411 => :length_required,
|
139
|
+
|
140
|
+
# WebDav extensions
|
141
|
+
422 => :unprocessable_entity,
|
142
|
+
}
|
143
|
+
|
144
|
+
def self.from_net_http_response(http)
|
145
|
+
status_code = http.code.to_i
|
146
|
+
body = http.body
|
147
|
+
|
148
|
+
headers =
|
149
|
+
CaseInsensitiveHash.from_hash(http.to_hash)
|
150
|
+
|
151
|
+
new(status_code, headers, body)
|
152
|
+
end
|
153
|
+
|
154
|
+
def content_type
|
155
|
+
headers['Content-Type'].split(';').first
|
156
|
+
end
|
157
|
+
|
158
|
+
def content_charset
|
159
|
+
(md = headers['Content-Type'].match(/;charset=(.*)/)) && md[1]
|
160
|
+
end
|
161
|
+
|
162
|
+
def status
|
163
|
+
STATI[status_code]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class CaseInsensitiveHash < Hash
|
168
|
+
def self.from_hash(hash)
|
169
|
+
hash.inject(new) do |m, (k, v)|
|
170
|
+
m[k] = v.first
|
171
|
+
m
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def []=(key, value)
|
176
|
+
super(key.downcase, value)
|
177
|
+
end
|
178
|
+
|
179
|
+
def [](key)
|
180
|
+
super(key.downcase)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
Have = Struct.new(:proxy) do
|
185
|
+
def method_missing(method_name, *arguments, &block)
|
186
|
+
proxy.send(method_name).must_equal(*arguments)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
::Object.send(:include, HyperSpec::ObjectExtensions)
|
193
|
+
::MiniTest::Spec.send(:include, HyperSpec::MiniTest::SpecExtensions)
|
@@ -0,0 +1,94 @@
|
|
1
|
+
---
|
2
|
+
- !ruby/struct:VCR::HTTPInteraction
|
3
|
+
request: !ruby/struct:VCR::Request
|
4
|
+
method: :get
|
5
|
+
uri: http://localhost:80/
|
6
|
+
headers:
|
7
|
+
body:
|
8
|
+
response: !ruby/struct:VCR::Response
|
9
|
+
http_version: "1.1"
|
10
|
+
status: !ruby/struct:VCR::ResponseStatus
|
11
|
+
code: 200
|
12
|
+
message: OK
|
13
|
+
headers:
|
14
|
+
content-length:
|
15
|
+
- "22"
|
16
|
+
content-type:
|
17
|
+
- application/json;charset=utf-8
|
18
|
+
body: |+
|
19
|
+
{"is-this-json":true}
|
20
|
+
|
21
|
+
- !ruby/struct:VCR::HTTPInteraction
|
22
|
+
request: !ruby/struct:VCR::Request
|
23
|
+
method: :head
|
24
|
+
uri: http://localhost:80/
|
25
|
+
headers:
|
26
|
+
body:
|
27
|
+
response: !ruby/struct:VCR::Response
|
28
|
+
http_version: "1.1"
|
29
|
+
status: !ruby/struct:VCR::ResponseStatus
|
30
|
+
code: 200
|
31
|
+
message: OK
|
32
|
+
headers:
|
33
|
+
content-length:
|
34
|
+
- "22"
|
35
|
+
content-type:
|
36
|
+
- application/json;charset=utf-8
|
37
|
+
body:
|
38
|
+
- !ruby/struct:VCR::HTTPInteraction
|
39
|
+
request: !ruby/struct:VCR::Request
|
40
|
+
method: :post
|
41
|
+
uri: http://localhost:80/
|
42
|
+
headers:
|
43
|
+
body:
|
44
|
+
response: !ruby/struct:VCR::Response
|
45
|
+
http_version: "1.1"
|
46
|
+
status: !ruby/struct:VCR::ResponseStatus
|
47
|
+
code: 200
|
48
|
+
message: OK
|
49
|
+
headers:
|
50
|
+
content-length:
|
51
|
+
- "22"
|
52
|
+
content-type:
|
53
|
+
- application/json;charset=utf-8
|
54
|
+
body: |+
|
55
|
+
{"is-this-json":true}
|
56
|
+
|
57
|
+
- !ruby/struct:VCR::HTTPInteraction
|
58
|
+
request: !ruby/struct:VCR::Request
|
59
|
+
method: :put
|
60
|
+
uri: http://localhost:80/
|
61
|
+
headers:
|
62
|
+
body:
|
63
|
+
response: !ruby/struct:VCR::Response
|
64
|
+
http_version: "1.1"
|
65
|
+
status: !ruby/struct:VCR::ResponseStatus
|
66
|
+
code: 200
|
67
|
+
message: OK
|
68
|
+
headers:
|
69
|
+
content-length:
|
70
|
+
- "22"
|
71
|
+
content-type:
|
72
|
+
- application/json;charset=utf-8
|
73
|
+
body: |+
|
74
|
+
{"is-this-json":true}
|
75
|
+
|
76
|
+
- !ruby/struct:VCR::HTTPInteraction
|
77
|
+
request: !ruby/struct:VCR::Request
|
78
|
+
method: :delete
|
79
|
+
uri: http://localhost:80/
|
80
|
+
headers:
|
81
|
+
body:
|
82
|
+
response: !ruby/struct:VCR::Response
|
83
|
+
http_version: "1.1"
|
84
|
+
status: !ruby/struct:VCR::ResponseStatus
|
85
|
+
code: 200
|
86
|
+
message: OK
|
87
|
+
headers:
|
88
|
+
content-length:
|
89
|
+
- "22"
|
90
|
+
content-type:
|
91
|
+
- application/json;charset=utf-8
|
92
|
+
body: |+
|
93
|
+
{"is-this-json":true}
|
94
|
+
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
gem 'minitest' # Ensure gem is used over built in.
|
4
|
+
require 'minitest/spec'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
|
7
|
+
require 'vcr'
|
8
|
+
require './spec/support/vcr'
|
9
|
+
require './spec/support/meta_spec'
|
10
|
+
|
11
|
+
$:.push File.expand_path("../../lib", __FILE__)
|
12
|
+
require 'hyperspec'
|
13
|
+
|
14
|
+
VCR.config do |c|
|
15
|
+
c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
|
16
|
+
c.stub_with :fakeweb
|
17
|
+
end
|
18
|
+
|
19
|
+
describe HyperSpec do
|
20
|
+
include MetaSpec
|
21
|
+
|
22
|
+
use_vcr_cassette('localhost')
|
23
|
+
|
24
|
+
it "should be of version 0.0.0" do
|
25
|
+
HyperSpec::VERSION.must_equal "0.0.0"
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "MiniTest::Spec extensions" do
|
29
|
+
describe "service" do
|
30
|
+
# `service` is added to `Kernel`, but conveniently enough
|
31
|
+
# returns a sub class of MiniTest::Spec which can be tested.
|
32
|
+
subject do
|
33
|
+
service("http://lolc.at") {}.
|
34
|
+
new("Required name.")
|
35
|
+
end
|
36
|
+
|
37
|
+
it { subject.must_be_kind_of MiniTest::Spec }
|
38
|
+
it { subject.base_uri.must_equal URI.parse("http://lolc.at/") }
|
39
|
+
it { subject.headers.must_equal({}) }
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "resource" do
|
43
|
+
# `resource` is added to `Kernel`, but conveniently enough
|
44
|
+
# returns a sub class of MiniTest::Spec which can be tested,
|
45
|
+
# though this time we must tap into the `service` and bind it.
|
46
|
+
subject do
|
47
|
+
the_spec do |bound|
|
48
|
+
service("http://lolc.at") do
|
49
|
+
bound.value = resource("/lolz") {}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it { subject.must_be_kind_of MiniTest::Spec }
|
55
|
+
it { subject.base_uri.must_equal URI.parse("http://lolc.at/lolz") }
|
56
|
+
it { subject.headers.must_equal({}) }
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "nested resource" do
|
60
|
+
# `resource` is added to `Kernel`, but conveniently enough
|
61
|
+
# returns a sub class of MiniTest::Spec which can be tested,
|
62
|
+
# though this time we must tap into the `service` and bind it.
|
63
|
+
subject do
|
64
|
+
the_spec do |bound|
|
65
|
+
service("http://lolc.at") do
|
66
|
+
resource("/lolz") do
|
67
|
+
bound.value = resource("catz") {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it { subject.base_uri.must_equal URI.parse("http://lolc.at/lolz/catz") }
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "with_headers" do
|
77
|
+
subject do
|
78
|
+
the_spec do |bound|
|
79
|
+
service("http://localhost") do
|
80
|
+
resource("/lolz") do
|
81
|
+
bound.value = with_headers({ 'X-Camel-Size' => 'LARGE' }) {}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it { subject.must_be_kind_of MiniTest::Spec }
|
88
|
+
it { subject.base_uri.must_equal URI.parse("http://localhost/lolz") }
|
89
|
+
it { subject.headers.must_equal({ 'X-Camel-Size' => 'LARGE' }) }
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "with_query" do
|
93
|
+
subject do
|
94
|
+
the_spec do |bound|
|
95
|
+
service("http://localhost") do
|
96
|
+
resource("/lolz") do
|
97
|
+
bound.value = with_query("q=monorail") {}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it { subject.must_be_kind_of MiniTest::Spec }
|
104
|
+
it { subject.base_uri.query.must_equal "q=monorail" }
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "with_request_body" do
|
108
|
+
subject do
|
109
|
+
the_spec do |bound|
|
110
|
+
service("http://localhost") do
|
111
|
+
resource("/lolz") do
|
112
|
+
bound.value = with_request_body("lol[title]=Roflcopter") {}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it { subject.must_be_kind_of MiniTest::Spec }
|
119
|
+
it { subject.request_body.must_equal "lol[title]=Roflcopter" }
|
120
|
+
end
|
121
|
+
|
122
|
+
%w[ get head post put delete ].map(&:to_sym).each do |http_method|
|
123
|
+
describe "HTTP method selection" do
|
124
|
+
subject do
|
125
|
+
the_spec do |bound|
|
126
|
+
service("http://localhost") do
|
127
|
+
resource("/") do
|
128
|
+
bound.value = send(http_method) {}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it { subject.request_type.must_equal http_method }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "response" do
|
139
|
+
subject do
|
140
|
+
the_spec do |bound|
|
141
|
+
service("http://localhost") do
|
142
|
+
resource("/") do
|
143
|
+
bound.value = get {}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end.response
|
147
|
+
end
|
148
|
+
|
149
|
+
it { subject.status_code.must_be_kind_of Integer }
|
150
|
+
it { subject.headers.must_be_kind_of Hash }
|
151
|
+
it { subject.body.must_be_kind_of String }
|
152
|
+
|
153
|
+
it { subject.content_type.must_be_kind_of String }
|
154
|
+
it { subject.status.must_be_kind_of Symbol }
|
155
|
+
|
156
|
+
describe "status" do
|
157
|
+
{
|
158
|
+
200 => :ok,
|
159
|
+
201 => :created,
|
160
|
+
401 => :unauthorized,
|
161
|
+
411 => :length_required,
|
162
|
+
422 => :unprocessable_entity,
|
163
|
+
}.each do |code, status|
|
164
|
+
it do
|
165
|
+
subject.status_code = code
|
166
|
+
subject.status.must_equal status
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "content_charset" do
|
172
|
+
it { subject.content_charset.must_be_kind_of String }
|
173
|
+
it do
|
174
|
+
subject.headers['Content-Type'] = "application/xml"
|
175
|
+
subject.content_charset.must_be_nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "header access" do
|
180
|
+
it "must be case insensitive" do
|
181
|
+
subject.headers['Content-Length'].must_equal \
|
182
|
+
subject.headers['content-length']
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "responds_with" do
|
188
|
+
subject do
|
189
|
+
the_spec do |bound|
|
190
|
+
service("http://localhost") do
|
191
|
+
resource("/") do
|
192
|
+
bound.value = get {}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end.responds_with
|
196
|
+
end
|
197
|
+
|
198
|
+
it { subject.status_code 200 }
|
199
|
+
it { subject.status :ok }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
data/spec/support/vcr.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
::MiniTest::Spec.send(:extend, VCR::RSpec::Macros)
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hyperspec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- "Hannes Tyd\xC3\xA9n"
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-02-07 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: minitest
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "2.11"
|
24
|
+
type: :development
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: vcr
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "1.6"
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: webmock
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: minitest
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "2.11"
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id004
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: vcr
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "1.6"
|
68
|
+
type: :runtime
|
69
|
+
version_requirements: *id005
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: webmock
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
type: :runtime
|
80
|
+
version_requirements: *id006
|
81
|
+
description: " By extending minitest/spec HyperSpec provides a Ruby DSL for testing HTTP APIs from the \"outside\".\n"
|
82
|
+
email:
|
83
|
+
- hannes@soundcloud.com
|
84
|
+
executables: []
|
85
|
+
|
86
|
+
extensions: []
|
87
|
+
|
88
|
+
extra_rdoc_files: []
|
89
|
+
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- Gemfile
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- examples/service/example.rb
|
96
|
+
- examples/service/example.ru
|
97
|
+
- examples/spec/example_spec.rb
|
98
|
+
- hyperspec.gemspec
|
99
|
+
- lib/hyperspec.rb
|
100
|
+
- lib/hyperspec/version.rb
|
101
|
+
- spec/fixtures/vcr_cassettes/localhost.yml
|
102
|
+
- spec/hyperspec_spec.rb
|
103
|
+
- spec/support/meta_spec.rb
|
104
|
+
- spec/support/vcr.rb
|
105
|
+
homepage: http://github.com/hannestyden/hyperspec
|
106
|
+
licenses: []
|
107
|
+
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: "0"
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: "0"
|
125
|
+
requirements: []
|
126
|
+
|
127
|
+
rubyforge_project: hyperspec
|
128
|
+
rubygems_version: 1.8.15
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: Full stack HTTP API testing DSL.
|
132
|
+
test_files:
|
133
|
+
- spec/fixtures/vcr_cassettes/localhost.yml
|
134
|
+
- spec/hyperspec_spec.rb
|
135
|
+
- spec/support/meta_spec.rb
|
136
|
+
- spec/support/vcr.rb
|