You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Sep 17, 2022. It is now read-only.
Copy file name to clipboardExpand all lines: README.md
+292-2Lines changed: 292 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -43,7 +43,7 @@ APId tests, or specs, are written in `jsonnet`. There are a number of built-in u
43
43
}
44
44
```
45
45
46
-
To run the test, issue
46
+
And to run the test
47
47
48
48
```bash
49
49
> apid check -s "example.jsonnet"
@@ -73,7 +73,297 @@ For more examples please check the [`examples`](examples) folder in this reposit
73
73
74
74
## 📚 Documentation
75
75
76
-
You can find all APId documentation in the [docs](docs) folder
76
+
APId tests, or specs, are written in `jsonnet`. There are a number of built-in useful functions to make it easier to make and validate requests to your API.
77
+
78
+
```jsonnet
79
+
// contents of `example.jsonnet`
80
+
81
+
{
82
+
simple_spec: spec([
83
+
{
84
+
name: "google homepage",
85
+
request: {
86
+
method: "GET",
87
+
url: "https://www.google.com/"
88
+
},
89
+
expect: {
90
+
code: 200
91
+
}
92
+
}
93
+
])
94
+
}
95
+
```
96
+
97
+
To run the test, issue
98
+
99
+
```bash
100
+
> apid check -s "example.jsonnet"
101
+
102
+
example::simple_spec
103
+
google homepage
104
+
+ status code is 200
105
+
106
+
specs passed: 1
107
+
specs failed: 0
108
+
```
109
+
110
+
Success! You've just written your first APId test! If you change the `expect.code` from `200` to lets say `500` the test will fail and this will be the output:
111
+
112
+
```bash
113
+
> apid check -s "example.jsonnet"
114
+
115
+
example::simple_spec
116
+
google homepage
117
+
o status code: wanted 500, got 200
118
+
119
+
specs passed: 0
120
+
specs failed: 1
121
+
```
122
+
123
+
## Structure
124
+
125
+
APId comes with a list of helpful functions that let you define what to expect from each response. Before looking into that, lets see what's the basic structure of a spec file.
126
+
127
+
Spec files are written in [jsonnet](https://jsonnet.org/). There is a helper function to define a spec, conveniently named `spec`.
128
+
129
+
A basic spec file returns a json object where each key is the name of the spec and it's value is a `spec`. For example
130
+
131
+
```jsonnet
132
+
{
133
+
spec_name: spec([])
134
+
}
135
+
```
136
+
137
+
The `spec` helper function takes a list of steps as a parameter. Those steps are executed sequentially, letting you model how your users interact with your API. If one step fails the rest won't be executed.
138
+
139
+
## Steps
140
+
141
+
A step represents a single API call. It is defined as a json object, for example
142
+
143
+
```js
144
+
{
145
+
name:'a descriptive identifier for this step',
146
+
request: {
147
+
type:'GET',
148
+
url:'https://www.google.com/',
149
+
headers: {
150
+
'haeder-name':'header-value'
151
+
},
152
+
body: {
153
+
'json body':'body can also be a json'
154
+
}
155
+
}
156
+
expect: {
157
+
code:200,
158
+
headers: {
159
+
'header-name':'header-value'
160
+
},
161
+
body: {
162
+
'json body':'body can also be a json'
163
+
}
164
+
}
165
+
}
166
+
```
167
+
168
+
This is pretty self explanatory, the only non-obvious thing might be that the `body` in both `request` and `expect` can be of any value - object, array, string, float, etc.
169
+
170
+
## Validation
171
+
172
+
### Matcher Translation
173
+
174
+
Matchers are a very versatile way of checking what value you got back. There is a list of matchers below, but before we get to them lets see how they work. APId transforms all keys and values in `expect.body`, `expect.headers` and `expect.body` blocks to matchers. This means that
175
+
176
+
```js
177
+
{
178
+
code:200
179
+
}
180
+
```
181
+
182
+
is the same as writing
183
+
184
+
```js
185
+
{
186
+
// more what float is below
187
+
code:float(200)
188
+
}
189
+
```
190
+
191
+
APId implicitly transforms raw values to matchers the following way
192
+
193
+
| JSON type | Matcher |
194
+
| --------- | -------- |
195
+
| Object |`json`|
196
+
| Number |`float`|
197
+
| String |`string`|
198
+
| Array |`array`|
199
+
200
+
If you want to enforce checks for a specific type you can manually specify which checker to use.
201
+
202
+
### Matchers
203
+
204
+
Here are all the matchers you can use and what parameters they take. The matchers are provided in the form `function(param: type = default_value)`
205
+
206
+
> Please note all matchers in this table are of type `matcher`
|`int(value: int)`| Checks if the value is an int with the provided value |`int(200)`|
213
+
|`float(value: float)`| Checks if the value is an float with the provided value |`float(88.36)`|
214
+
|`regex(regex: string)`| Checks if a string matches the provided regex |`regex("\\w+")`|
215
+
|`json(object: map, subset: bool = false)`| Checks if the value is an object. When subset is false, the received value can have extra keys not present in the provided object |`json({ some: "value" })`|
216
+
|`array(array: map, subset: bool = false)`| Checks if the value is an array. When subset is false, the received value can have extra values not present in the provided array |`array(["value", { another: "value" }])`|
217
+
|`len(length: int)`| Checks if the length of the value matches. Can be used on `string`, `object` and `array`, otherwise fails |`len(3)`|
218
+
|`range(from: float, to: float)`| Checks if the value is more than or equal to `from` and less than or equal to `to`|`range(3.0, 8.0)`|
219
+
220
+
### Boolean matchers
221
+
222
+
There are two extra matchers provided for complex situations. These are the `and` and `or` matchers.
223
+
224
+
> Please note all matchers in this table are of type `matcher` allowing you to nest them indefinitely
|`and(matchers: []matcher)`| Checks if the value matches all provided matchers |`all([ type.int, range(3.0, 8.0) ])`|
229
+
|`or(matchers: []matcher)`| Checks if the value matches any one of the provided matchers |`or([ type.int, range(3.0, 8.0) ])`|
230
+
231
+
Writing complex matchers
232
+
233
+
```js
234
+
// With the boolean matchers you can write something like
235
+
body: {
236
+
key:and([
237
+
type.object,
238
+
len(3),
239
+
or([
240
+
{
241
+
nested_key:regex("\\w+")
242
+
},
243
+
{
244
+
nested_key:type.int
245
+
}
246
+
])
247
+
])
248
+
}
249
+
```
250
+
251
+
The example above would pass only if the value of `key` is an object with three keys, one of which has a key with value `nested_key` and is either matching `\w+` or is an int. This might not be the best use of complex matchers, but it shows you how powerful they are.
252
+
253
+
### Key matchers
254
+
255
+
JSON keys are strings. In most cases it's more than enough to do an `equals` comparison, but in some cases you might want to check if there is a key that matches a specific regex for example. To define a key matcher the only thing you need to do is encapsulate the matchers you want in a `key()`.
|`key(matcher: matcher)`| Checks if a key matches the provided matcher |`key(regex("\w+"))`|
260
+
261
+
A matcher is any valid matcher, though some don't make sense to be used here e.g. you can't have an object as a key, but `key(json({ key: "value" }))` is a valid matcher. It will always fail, but won't cause compile issues.
262
+
263
+
An example of a complex key matcher would be
264
+
265
+
```js
266
+
{
267
+
body: {
268
+
key(
269
+
or([
270
+
regex("\\w+"),
271
+
regex("\\d+"),
272
+
])
273
+
):"the value of that key"
274
+
}
275
+
}
276
+
```
277
+
278
+
### Typechecks
279
+
280
+
If you don't care about the value, you can just check if a certain filed is of a certain type. APId provides a `types` object that has basic type matchers. For example
281
+
282
+
> Please note type checkers are not functions, but constants instead!
283
+
284
+
```js
285
+
{
286
+
// check that the value is a float with value `467` (automatically casts ints to floats when checking)
287
+
body: {
288
+
'key':467
289
+
}
290
+
}
291
+
```
292
+
293
+
```js
294
+
{
295
+
// will check that the value is an integer with value `467`
296
+
body: {
297
+
'key':467
298
+
}
299
+
}
300
+
```
301
+
302
+
```js
303
+
{
304
+
// will check that the value is an integer and ignore the value
305
+
body: {
306
+
'key':type.int
307
+
}
308
+
}
309
+
```
310
+
311
+
Here is a list of the type matchers available
312
+
313
+
| Matcher |
314
+
| ------------- |
315
+
|`type.int`|
316
+
|`type.float`|
317
+
|`type.string`|
318
+
|`type.object`|
319
+
|`type.array`|
320
+
321
+
## Patterns
322
+
323
+
Jsonnet is a very powerful language which can be utilised to make your life easier.
324
+
325
+
### Split variables from tests
326
+
327
+
For example you can extract any variables in a separate file
328
+
329
+
```js
330
+
// vars.libsonnet
331
+
{
332
+
url:'http://localhost:8080',
333
+
}
334
+
335
+
// test.jsonnet
336
+
{
337
+
name:'request',
338
+
request: {
339
+
url:vars.url,
340
+
},
341
+
expect: {
342
+
code:200,
343
+
},
344
+
},
345
+
```
346
+
347
+
### Store matchers in variables
348
+
349
+
You can extract your matchers in a local variable to make the test easier to read
350
+
351
+
```js
352
+
// test.jsonnet
353
+
local key_matcher =key(
354
+
or([
355
+
regex("\\w+"),
356
+
regex("\\d+"),
357
+
])
358
+
);
359
+
360
+
{
361
+
body: {
362
+
[key_matcher]:"the value of that key"// note the [] around the key, ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names
0 commit comments