airborne 0.0.17 → 0.0.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +161 -108
- data/airborne.gemspec +1 -1
- data/lib/airborne/path_matcher.rb +3 -3
- data/lib/airborne/request_expectations.rb +4 -2
- data/spec/airborne/expect_json_types_spec.rb +6 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c02499ba105b2a0cf64b2b3f66b3cb0603cdecff
|
4
|
+
data.tar.gz: 80406ace55d569bbabfd7a103f0135e4a37357b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a1e7465a8e9b57cd98b8ac94c93f6cf0c890cf660905da806c3a768ef36a0f897a143c35afa52a704b6a2ed9bf8b0e4b3a87825e7c47e4b134ae74baf8caadd
|
7
|
+
data.tar.gz: 4bbccbf924040c02119972fab3582cab6b71119d8696e0a385f8840389f3023b8ceb23af6d91606d34a50fa98416a081b015b9901573fe04c665d2a257e49993
|
data/README.md
CHANGED
@@ -8,25 +8,32 @@
|
|
8
8
|
RSpec driven API testing framework inspired by [frisby.js](https://github.com/vlucas/frisby)
|
9
9
|
|
10
10
|
## Installation
|
11
|
+
|
11
12
|
Install Airborne:
|
12
13
|
|
13
14
|
gem install airborne
|
15
|
+
|
16
|
+
Or add it to your Gemfile:
|
17
|
+
|
18
|
+
gem 'airborne'
|
14
19
|
|
15
20
|
##Creating Tests
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
22
|
+
```ruby
|
23
|
+
require 'airborne'
|
24
|
+
|
25
|
+
describe 'sample spec' do
|
26
|
+
it 'should validate types' do
|
27
|
+
get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
|
28
|
+
expect_json_types({name: :string})
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should validate values' do
|
32
|
+
get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
|
33
|
+
expect_json({:name => "John Doe"})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
30
37
|
|
31
38
|
When calling expect_json_types, these are the valid types that can be tested against:
|
32
39
|
|
@@ -45,31 +52,37 @@ When calling expect_json_types, these are the valid types that can be tested aga
|
|
45
52
|
|
46
53
|
If the properties are optional and may not appear in the response, you can append `_or_null` to the types above.
|
47
54
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
```ruby
|
56
|
+
describe 'sample spec' do
|
57
|
+
it 'should validate types' do
|
58
|
+
get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" } or { "name" : "John Doe", "age" : 45 }
|
59
|
+
expect_json_types({name: :string, age: :int_or_null})
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
54
63
|
|
55
64
|
Additionally, if an entire object could be null, but you'd still want to test the types if it does exist, you can wrap the expectations in a call to `optional`:
|
56
65
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
66
|
+
```ruby
|
67
|
+
it 'should allow optional nested hash' do
|
68
|
+
get '/simple_path_get' #may or may not return coordinates
|
69
|
+
expect_json_types("address.coordinates", optional({lattitude: :float, longitutde: :float}))
|
70
|
+
end
|
71
|
+
```
|
61
72
|
|
62
|
-
When calling `expect_json`, you can optionally provide a block and run your own `rspec` expectations:
|
73
|
+
When calling `expect_json` or `expect_json_types`, you can optionally provide a block and run your own `rspec` expectations:
|
63
74
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
75
|
+
```ruby
|
76
|
+
describe 'sample spec' do
|
77
|
+
it 'should validate types' do
|
78
|
+
get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
|
79
|
+
expect_json({name: -> (name){expect(name.length).to eq(8)}})
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
71
83
|
|
72
84
|
##Making requests
|
85
|
+
|
73
86
|
Airborne uses `rest_client` to make the HTTP request, and supports all HTTP verbs. When creating a test, you can call any of the following methods: `get`, `post`, `put`, `patch`, `delete`. This will then give you access the following properties:
|
74
87
|
|
75
88
|
* `response` - The HTTP response returned from the request
|
@@ -79,22 +92,28 @@ Airborne uses `rest_client` to make the HTTP request, and supports all HTTP verb
|
|
79
92
|
|
80
93
|
Fo example:
|
81
94
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
95
|
+
```ruby
|
96
|
+
it 'should validate types' do
|
97
|
+
get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
|
98
|
+
name = json_body[:name] #name will equal "John Doe"
|
99
|
+
body_as_string = body
|
100
|
+
end
|
101
|
+
```
|
88
102
|
|
89
103
|
When calling any of the methods above, you can pass request headers to be used.
|
90
104
|
|
91
|
-
|
105
|
+
```ruby
|
106
|
+
get 'http://example.com/api/v1/my_api', {'x-auth-token' => 'my_token'}
|
107
|
+
```
|
92
108
|
|
93
109
|
For requests that require a body (`post`, `put`, `patch`) you can pass the body as a hash as well:
|
94
110
|
|
95
|
-
|
96
|
-
|
111
|
+
```ruby
|
112
|
+
post 'http://example.com/api/v1/my_api', {:name => 'John Doe'}, {'x-auth-token' => 'my_token'}
|
113
|
+
```
|
114
|
+
|
97
115
|
##API
|
116
|
+
|
98
117
|
* `expect_json_types` - Tests the types of the JSON property values returned
|
99
118
|
* `expect_json` - Tests the values of the JSON property values returned
|
100
119
|
* `expect_json_keys` - Tests the existence of the specified keys in the JSON object
|
@@ -103,114 +122,148 @@ For requests that require a body (`post`, `put`, `patch`) you can pass the body
|
|
103
122
|
* `expect_header_contains` - Partial match test on a specified header
|
104
123
|
|
105
124
|
##Path Matching
|
125
|
+
|
106
126
|
When calling `expect_json_types`, `expect_json` or `expect_json_keys` you can optionaly specify a path as a first parameter.
|
107
127
|
|
108
128
|
For example, if our API returns the following JSON:
|
109
129
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
}
|
130
|
+
```json
|
131
|
+
{
|
132
|
+
"name": "Alex",
|
133
|
+
"address": {
|
134
|
+
"street": "Area 51",
|
135
|
+
"city": "Roswell",
|
136
|
+
"state": "NM",
|
137
|
+
"coordinates": {
|
138
|
+
"lattitude": 33.3872,
|
139
|
+
"longitude": 104.5281
|
121
140
|
}
|
141
|
+
}
|
142
|
+
}
|
143
|
+
```
|
122
144
|
|
123
145
|
This test would only test the address object:
|
124
146
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
147
|
+
```ruby
|
148
|
+
describe 'path spec' do
|
149
|
+
it 'should allow simple path and verify only that path' do
|
150
|
+
get 'http://example.com/api/v1/simple_path_get'
|
151
|
+
expect_json_types('address', {street: :string, city: :string, state: :string, coordinates: :object })
|
152
|
+
#or this
|
153
|
+
expect_json_types('address', {street: :string, city: :string, state: :string, coordinates: { lattitude: :float, longitude: :float } })
|
154
|
+
end
|
155
|
+
end
|
156
|
+
```
|
133
157
|
|
134
158
|
Alternativley, if we only want to test `coordinates` we can dot into just the `coordinates`:
|
135
159
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
160
|
+
```ruby
|
161
|
+
it 'should allow nested paths' do
|
162
|
+
get 'http://example.com/api/v1/simple_path_get'
|
163
|
+
expect_json('address.coordinates', {lattitude: 33.3872, longitutde: 104.5281} )
|
164
|
+
end
|
165
|
+
```
|
140
166
|
|
141
167
|
When dealing with `arrays`, we can optionally test all (`*`) or a single (`?` - any, `0` - index) element of the array:
|
142
168
|
|
143
169
|
Given the following JSON:
|
144
170
|
|
171
|
+
```json
|
172
|
+
{
|
173
|
+
"cars": [
|
174
|
+
{
|
175
|
+
"make": "Tesla",
|
176
|
+
"model": "Model S"
|
177
|
+
},
|
145
178
|
{
|
146
|
-
|
147
|
-
|
148
|
-
{"make": "Lamborghini", "model": "Aventador"}
|
149
|
-
]
|
179
|
+
"make": "Lamborghini",
|
180
|
+
"model": "Aventador"
|
150
181
|
}
|
182
|
+
]
|
183
|
+
}
|
184
|
+
```
|
151
185
|
|
152
186
|
We can test against just the first car like this:
|
153
187
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
188
|
+
```ruby
|
189
|
+
it 'should index into array and test against specific element' do
|
190
|
+
get '/array_api'
|
191
|
+
expect_json('cars.0', {make: "Tesla", model: "Model S"})
|
192
|
+
end
|
193
|
+
```
|
158
194
|
|
159
195
|
To test the types of all elements in the array:
|
160
196
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
197
|
+
```ruby
|
198
|
+
it 'should test all elements of the array' do
|
199
|
+
get 'http://example.com/api/v1/array_api'
|
200
|
+
expect_json('cars.?', {make: "Tesla", model: "Model S"}) # tests that one car in array matches the tesla
|
201
|
+
expect_json_types('cars.*', {make: :string, model: :string}) # tests all cars in array for make and model of type string
|
202
|
+
end
|
203
|
+
```
|
166
204
|
|
167
205
|
`*` and `?` work for nested arrays as well. Given the following JSON:
|
168
206
|
|
207
|
+
```json
|
208
|
+
{
|
209
|
+
"cars": [
|
169
210
|
{
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
211
|
+
"make": "Tesla",
|
212
|
+
"model": "Model S",
|
213
|
+
"owners": [
|
214
|
+
{
|
215
|
+
"name": "Bart Simpson"
|
216
|
+
}
|
217
|
+
]
|
218
|
+
},
|
219
|
+
{
|
220
|
+
"make": "Lamborghini",
|
221
|
+
"model": "Aventador",
|
222
|
+
"owners": [
|
223
|
+
{
|
224
|
+
"name": "Peter Griffin"
|
225
|
+
}
|
226
|
+
]
|
183
227
|
}
|
228
|
+
]
|
229
|
+
}
|
230
|
+
```
|
184
231
|
|
185
232
|
===
|
186
233
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
234
|
+
```ruby
|
235
|
+
it 'should check all nested arrays for specified elements' do
|
236
|
+
get 'http://example.com/api/v1/array_with_nested'
|
237
|
+
expect_json_types('cars.*.owners.*', {name: :string})
|
238
|
+
end
|
239
|
+
```
|
192
240
|
|
193
241
|
##Configuration
|
194
242
|
|
195
243
|
When setting up Airborne, you can call `configure` just like you would with `rspec`:
|
196
244
|
|
197
|
-
|
198
|
-
|
199
|
-
|
245
|
+
```ruby
|
246
|
+
#config is the RSpec configuration and can be used just like it
|
247
|
+
Airborne.configure.do |config|
|
248
|
+
config.include MyModule
|
249
|
+
end
|
250
|
+
```
|
200
251
|
|
201
252
|
Additionally, you can specify a `base_url` and default `headers` to be used on every request (unless overriden in the actual request):
|
202
253
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
254
|
+
```ruby
|
255
|
+
Airborne.configure.do |config|
|
256
|
+
config.base_url = 'http://example.com/api/v1'
|
257
|
+
config.headers = {'x-auth-token' => 'my_token'}
|
258
|
+
end
|
259
|
+
|
260
|
+
describe 'spec' do
|
261
|
+
it 'now we no longer need the full url' do
|
262
|
+
get '/simple_get'
|
263
|
+
expect_json_types({name: :string})
|
264
|
+
end
|
265
|
+
end
|
266
|
+
```
|
214
267
|
|
215
268
|
## License
|
216
269
|
|
data/airborne.gemspec
CHANGED
@@ -7,9 +7,9 @@ module Airborne
|
|
7
7
|
if part == '*' || part == '?'
|
8
8
|
type = part
|
9
9
|
raise "Expected #{path} to be array got #{json.class} from JSON response" unless json.class == Array
|
10
|
-
if index < parts.length
|
10
|
+
if index < parts.length.pred
|
11
11
|
json.each do |element|
|
12
|
-
sub_path = parts[(index
|
12
|
+
sub_path = parts[(index.next)...(parts.length)].join('.')
|
13
13
|
get_by_path(sub_path, element, &block)
|
14
14
|
end
|
15
15
|
return
|
@@ -21,7 +21,7 @@ module Airborne
|
|
21
21
|
json = json[part]
|
22
22
|
else
|
23
23
|
json = json[part.to_sym]
|
24
|
-
raise "Expected #{path} to be object or array got #{json.class} from JSON response" unless
|
24
|
+
raise "Expected #{path} to be object or array got #{json.class} from JSON response" unless [Array, Hash, NilClass].include?(json.class)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
if type == '*'
|
@@ -97,6 +97,8 @@ module Airborne
|
|
97
97
|
value = hash[prop_name]
|
98
98
|
if expected_type.class == Hash || expected_type.class == Airborne::OptionalHashTypeExpectations
|
99
99
|
expect_json_types_impl(expected_type, value)
|
100
|
+
elsif expected_type.class == Proc
|
101
|
+
expected_type.call(value)
|
100
102
|
elsif expected_type.to_s.include?("array_of")
|
101
103
|
expect(value.class).to eq(Array), "Expected #{prop_name} to be of type #{expected_type}, got #{value.class} instead"
|
102
104
|
value.each do |val|
|
@@ -111,9 +113,9 @@ module Airborne
|
|
111
113
|
def expect_json_impl(expectations, hash)
|
112
114
|
expectations.each do |prop_name, expected_value|
|
113
115
|
actual_value = hash[prop_name]
|
114
|
-
if
|
116
|
+
if expected_value.class == Hash
|
115
117
|
expect_json_impl(expected_value, actual_value)
|
116
|
-
elsif
|
118
|
+
elsif expected_value.class == Proc
|
117
119
|
expected_value.call(actual_value)
|
118
120
|
else
|
119
121
|
expect(expected_value).to eq(actual_value)
|
@@ -96,4 +96,10 @@ describe 'expect_json_types' do
|
|
96
96
|
get '/simple_path_get'
|
97
97
|
expect_json_types("address.coordinates", optional({lattitude: :float, longitutde: :float}))
|
98
98
|
end
|
99
|
+
|
100
|
+
it 'should invoke proc passed in' do
|
101
|
+
mock_get('simple_get')
|
102
|
+
get '/simple_get'
|
103
|
+
expect_json_types({name: -> (name){expect(name.length).to eq(4)}})
|
104
|
+
end
|
99
105
|
end
|