Skip to content
This repository was archived by the owner on Sep 17, 2022. It is now read-only.

Commit d622cb4

Browse files
authored
Update README.md
1 parent cfd33db commit d622cb4

File tree

1 file changed

+292
-2
lines changed

1 file changed

+292
-2
lines changed

README.md

Lines changed: 292 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ APId tests, or specs, are written in `jsonnet`. There are a number of built-in u
4343
}
4444
```
4545

46-
To run the test, issue
46+
And to run the test
4747

4848
```bash
4949
> apid check -s "example.jsonnet"
@@ -73,7 +73,297 @@ For more examples please check the [`examples`](examples) folder in this reposit
7373

7474
## 📚 Documentation
7575

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`
207+
208+
| Matcher | Description | Example |
209+
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
210+
| `any()` | Matches any value. Use this when you want to check for the existence of a key or value | `any()` |
211+
| `string(value: string, case_sensitive: bool = true)` | Matches a string | `string("a string value")` |
212+
| `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
225+
226+
| Matcher | Description | Example |
227+
| -------------------------- | ------------------------------------------------------------ | ------------------------------------ |
228+
| `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()`.
256+
257+
| Matcher | Description | Example |
258+
| ----------------------- | -------------------------------------------- | ------------------- |
259+
| `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
363+
}
364+
}
365+
```
366+
77367

78368
## ⁉️ Help
79369

0 commit comments

Comments
 (0)