airborne 0.0.17 → 0.0.18
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.
- 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
|