brine-dsl 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +123 -0
- data/Guardfile +12 -0
- data/LICENSE +21 -0
- data/README.md +137 -0
- data/Rakefile +32 -0
- data/brine-dsl.gemspec +32 -0
- data/config/cucumber.yml +2 -0
- data/docs/build.gradle +19 -0
- data/docs/cookbook.html +567 -0
- data/docs/gradle/wrapper/gradle-wrapper.jar +0 -0
- data/docs/gradle/wrapper/gradle-wrapper.properties +6 -0
- data/docs/gradlew +172 -0
- data/docs/gradlew.bat +84 -0
- data/docs/guide.html +1149 -0
- data/docs/index.html +472 -0
- data/docs/specs.html +1672 -0
- data/docs/src/cookbook.adoc +87 -0
- data/docs/src/guide.adoc +427 -0
- data/docs/src/index.adoc +16 -0
- data/docs/src/spec.erb +121 -0
- data/docs/src/specs.adoc +24 -0
- data/features/argument_transforms/boolean.feature +37 -0
- data/features/argument_transforms/datetime.feature +45 -0
- data/features/argument_transforms/integer.feature +41 -0
- data/features/argument_transforms/list.feature +46 -0
- data/features/argument_transforms/object.feature +66 -0
- data/features/argument_transforms/quoted.feature +41 -0
- data/features/argument_transforms/regex.feature +40 -0
- data/features/argument_transforms/template.feature +46 -0
- data/features/argument_transforms/whitespace.feature +51 -0
- data/features/assertions/is_a_valid.feature +184 -0
- data/features/assertions/is_equal_to.feature +60 -0
- data/features/assertions/is_including.feature +29 -0
- data/features/assertions/is_matching.feature +35 -0
- data/features/deprecations/replaced_with.feature +35 -0
- data/features/request_construction/basic.feature +29 -0
- data/features/request_construction/body.feature +26 -0
- data/features/request_construction/clearing.feature +46 -0
- data/features/request_construction/headers.feature +94 -0
- data/features/request_construction/params.feature +60 -0
- data/features/resource_cleanup/cleanup.feature +86 -0
- data/features/selectors/all.feature +55 -0
- data/features/selectors/any.feature +48 -0
- data/features/step_definitions/test_steps.rb +5 -0
- data/features/support/env.rb +10 -0
- data/lib/brine/cleaner_upper.rb +62 -0
- data/lib/brine/coercer.rb +18 -0
- data/lib/brine/hooks.rb +4 -0
- data/lib/brine/mustache_binder.rb +25 -0
- data/lib/brine/requester.rb +125 -0
- data/lib/brine/rest_steps.rb +138 -0
- data/lib/brine/selector.rb +66 -0
- data/lib/brine/step_definitions/assertions.rb +37 -0
- data/lib/brine/step_definitions/assignment.rb +13 -0
- data/lib/brine/step_definitions/cleanup.rb +4 -0
- data/lib/brine/step_definitions/request_construction.rb +19 -0
- data/lib/brine/step_definitions/selection.rb +37 -0
- data/lib/brine/test_steps.rb +138 -0
- data/lib/brine/transforms.rb +81 -0
- data/lib/brine/type_checks.rb +35 -0
- data/lib/brine/util.rb +35 -0
- data/lib/brine.rb +39 -0
- data/tutorial/missing.feature +5 -0
- data/tutorial/post_matching.feature +12 -0
- data/tutorial/post_status.feature +10 -0
- data/tutorial/support/env.rb +2 -0
- metadata +306 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
= icon:cutlery[] Brine Cookbook
|
2
|
+
Matt Whipple <http://github.com/mwhipple[@mwhipple]>
|
3
|
+
:description: Cookbook for the Brine REST Testing DSL
|
4
|
+
:keywords: Brine, Cucumber, REST, DSL
|
5
|
+
|
6
|
+
== Overview
|
7
|
+
The following are some recipes to address issues which may arise while using
|
8
|
+
Brine but are not considered part of Brine's responsibility (at least presently).
|
9
|
+
Many of these are focused on simplicity and ease rather than robustness or elegance,
|
10
|
+
and modification to address specific cases should be expected.
|
11
|
+
|
12
|
+
== "Assurances"
|
13
|
+
=== Context
|
14
|
+
Ideally all tests should be as self-contained and isolated as possible;
|
15
|
+
when writing functional tests, however, there are cases where this isn't
|
16
|
+
feasible or possible. In some cases a system depends on another external
|
17
|
+
system which is not a system that is under test and which (for whatever reason)
|
18
|
+
cannot be easily worked with. In white box testing such a system would likely be
|
19
|
+
represented by some form of test double, but this may be unfeasible and/or
|
20
|
+
undesirable when testing a deployed system.
|
21
|
+
|
22
|
+
An example of such a system is user/account management which often incurs
|
23
|
+
additional overhead to provision a new account. When testing a secured
|
24
|
+
system valid accounts are needed for representative testing, but provisioning
|
25
|
+
a new account may be difficult or outside the scope of the system that is being
|
26
|
+
actively tested. If tested functionality involves enacting account-wide changes
|
27
|
+
and the number of accounts is limited, then that is likely to unfortunately prevent
|
28
|
+
complete test isolation.
|
29
|
+
|
30
|
+
=== Solution
|
31
|
+
In such cases a standard solution is to designate certain resources to be reused for
|
32
|
+
certain tests. These are analogous to the concept of "fixtures" in some test suites
|
33
|
+
though there may be slight differences in implementation and reliance on them.
|
34
|
+
Here the term "assurances" is used..primarily because it starts with `a` which lends
|
35
|
+
itself to relevant files being listed towards the beginning alphabetically in a given directory.
|
36
|
+
|
37
|
+
The goal of assurances is to specify conditions which are expected before other
|
38
|
+
tests are to be run. Preferably the dependent tests should also explicitly declare the
|
39
|
+
dependency but a significant solution for that is not established. Assurances therefore
|
40
|
+
validate that preconditions are met; ideally if such preconditions can be established
|
41
|
+
idempotently then the assurances can do so before the validation.
|
42
|
+
|
43
|
+
==== Assurances are NOT Tests
|
44
|
+
**Assurances validate a state which is desired to be consistently retained within the
|
45
|
+
system rather than being changed**. This means that they should _not_ be used for tests
|
46
|
+
as that would require state changes, nor should they clean up after themselves (as that
|
47
|
+
would also imply a state change). If assurances are configured for a system which should
|
48
|
+
also be tested, then appropriate tests should exist (including those that may validate any
|
49
|
+
behavior relied upon by the assurance).
|
50
|
+
|
51
|
+
==== Consequences
|
52
|
+
As mentioned previously assertions help in cases where tests cannot be fully isolated,
|
53
|
+
and therefore some known state must be established and reused across tests (and such state
|
54
|
+
should *not* change). A practical reason for this is to allow for overlapping test executions.
|
55
|
+
If tests are not fully isolated, state is being changed, and test runs overlap, then tests
|
56
|
+
may fail non-deterministically due to one test run pulling the state out from another. This
|
57
|
+
in the simplest form can be a nuisance but it also effectively precludes the ability to speed
|
58
|
+
up test runs through the use of parallelism/asynchronicity.
|
59
|
+
|
60
|
+
_TODO: Enumerate drawbacks_
|
61
|
+
|
62
|
+
=== Recipe
|
63
|
+
This can be done using standard cucumber tags. Assurances can be defined in designated
|
64
|
+
`assure_*.feature` files where each Feature is appropriately tagged:
|
65
|
+
[source,gherkin]
|
66
|
+
----
|
67
|
+
@assure
|
68
|
+
Feature: Some preconditions are verified...
|
69
|
+
----
|
70
|
+
And then a Rake task is added to run those tagged features:
|
71
|
+
[source,ruby]
|
72
|
+
----
|
73
|
+
Cucumber::Rake::Task.new(:assure) do |t|
|
74
|
+
t.cucumber_opts = "#{ENV['CUCUMBER_OPTS']} --tags @assure"
|
75
|
+
end
|
76
|
+
----
|
77
|
+
The Rake task that runs the other tests then depends on that task:
|
78
|
+
[source,ruby]
|
79
|
+
----
|
80
|
+
task :invoke_cuke => [:assure] do
|
81
|
+
#Run cucumber, potentially in parallel and likely with --tags `@assure`
|
82
|
+
end
|
83
|
+
----
|
84
|
+
This approach tests preconditions and will avoid running the rest of the tests if they
|
85
|
+
are not (relying on standard Rake behavior). The assurances can also be run with different
|
86
|
+
Cucumber behavior so that the full test suite can be more stochastic
|
87
|
+
(randomized/non-serialized) while the assurances can be more controlled.
|
data/docs/src/guide.adoc
ADDED
@@ -0,0 +1,427 @@
|
|
1
|
+
= icon:book[] Brine User Guide
|
2
|
+
Matt Whipple <http://github.com/mwhipple[@mwhipple]>
|
3
|
+
:description: The User Guide for using the Brine REST Testing DSL
|
4
|
+
:keywords: Brine, Cucumber, REST, DSL
|
5
|
+
:grave: `
|
6
|
+
|
7
|
+
Cucumber DSL for testing REST APIs
|
8
|
+
|
9
|
+
== Introduction
|
10
|
+
|
11
|
+
=== Motivation
|
12
|
+
REpresentational State Transfer APIs expose their functionality
|
13
|
+
through combinations of fairly coarse primitives that generally
|
14
|
+
revolve around the use of transferring data in a standard exchange
|
15
|
+
format (such as JSON) using HTTP methods and other aspects of the very
|
16
|
+
simple HTTP protocol. Tests for such an API can therefore be defined
|
17
|
+
using a domain specific language (DSL) built around those higher level
|
18
|
+
ideas rather than requiring a general purpose language (the equivalent
|
19
|
+
of scripted `curl` commands with some glue code and assertions).
|
20
|
+
This project provides such a DSL by using select libraries
|
21
|
+
integrated into Cucumber, where Cucumber provides a test-oriented
|
22
|
+
framework for DSL creation.
|
23
|
+
|
24
|
+
=== Sample Usage
|
25
|
+
The general usage pattern revolves around construction of a request
|
26
|
+
and performing assertions against the received response.
|
27
|
+
|
28
|
+
[source,gherkin]
|
29
|
+
----
|
30
|
+
When the request body is assigned:
|
31
|
+
"""
|
32
|
+
{"first_name": "John",
|
33
|
+
"last_name": "Smith"}
|
34
|
+
"""
|
35
|
+
And a POST is sent to `/users`
|
36
|
+
Then the value of the response status is equal to `200`
|
37
|
+
And the value of the response body is including:
|
38
|
+
"""
|
39
|
+
{"first_name": "John",
|
40
|
+
"last_name": "Smith"}
|
41
|
+
"""
|
42
|
+
----
|
43
|
+
|
44
|
+
=== Key Features
|
45
|
+
Variable Binding/Expansion::
|
46
|
+
In cases where dynamic data is in the response or is desired for the
|
47
|
+
request, then values can be bound to identifiers which can then be
|
48
|
+
expanded using http://mustache.github.io[Mustache] templates in your
|
49
|
+
feature files.
|
50
|
+
|
51
|
+
Type Transforms::
|
52
|
+
Different types of data can be expressed directly in the feature files
|
53
|
+
or expanded into variables by using the appropriate syntax for that
|
54
|
+
type.
|
55
|
+
|
56
|
+
Type Coercion::
|
57
|
+
Related to transforms, a facility to coerce types is also provided. This allows
|
58
|
+
more intelligent comparison of inputs which have been transformed to a
|
59
|
+
richer data type with those that have not been transformed (normally strings).
|
60
|
+
As an example comparing a date/time value with a string will attempt to parse
|
61
|
+
the string to a date/time so that the values can be compared using the proper semantics.
|
62
|
+
|
63
|
+
<<_resource_cleanup>>::
|
64
|
+
Tests are likely to create resources which should then be cleaned up,
|
65
|
+
restoring the pre-test state of the system: steps to facilitate this
|
66
|
+
are provided.
|
67
|
+
|
68
|
+
Authentication::
|
69
|
+
Presently OAuth2 is supported to issue authenticated requests during a
|
70
|
+
test (likely using a feature `Background`).
|
71
|
+
|
72
|
+
Request Construction and Response Assertion Step Definitions::
|
73
|
+
The previous features combined with the library of provide steps should
|
74
|
+
cover all of the functionality needed to exercise and validate all of
|
75
|
+
the functionality exposed by your REST API.
|
76
|
+
|
77
|
+
== Installation
|
78
|
+
Presently the gem for this project isn't being published anywhere:
|
79
|
+
primarily because tracking down a local gem repository seems scary. If
|
80
|
+
this project is open sourced then this will probably change but in the
|
81
|
+
meantime the project can be used off of GitHub by adding this to your
|
82
|
+
`Gemfile` and performing the usual `bundle install` dance:
|
83
|
+
|
84
|
+
[source,ruby]
|
85
|
+
----
|
86
|
+
git 'git@github.com:brightcove/brine.git', :branch => 'master' do
|
87
|
+
gem 'brine'
|
88
|
+
end
|
89
|
+
----
|
90
|
+
|
91
|
+
Specific branches and refs can be targetted as
|
92
|
+
documented http://bundler.io/git.html[here]. This should likely be
|
93
|
+
done in any cases where you're not actively tracking Brine and don't want
|
94
|
+
your tests to suddenly break because of changes to it.
|
95
|
+
|
96
|
+
Brine can then be "mixed in" to your project (which adds assorted
|
97
|
+
modules to the `World` and loads all the step definitions and other
|
98
|
+
Cucumber magic) by adding the following to your `support/env.rb` or
|
99
|
+
other ruby file:
|
100
|
+
|
101
|
+
[source,ruby]
|
102
|
+
----
|
103
|
+
require 'brine'
|
104
|
+
|
105
|
+
World(brine_mix)
|
106
|
+
----
|
107
|
+
|
108
|
+
Select pieces can also be loaded (to be documented). With the above,
|
109
|
+
feature files should be able to be written and executed without
|
110
|
+
requiring any additional ruby code.
|
111
|
+
|
112
|
+
== Tutorial
|
113
|
+
We'll write some tests against http://myjson.com/api
|
114
|
+
(selected fairly arbitrary from the list at https://github.com/toddmotto/public-apis).
|
115
|
+
The API is being explored for the sake of this tutorial,
|
116
|
+
which also serves to bolster this library to support the effort.
|
117
|
+
|
118
|
+
=== Selecting a ROOT_URL
|
119
|
+
Brine expects steps to use relative URLs. The feature files specify
|
120
|
+
the behavior of an API (or multiple APIs), while the root of the
|
121
|
+
URLs define where that API is, so this is a natural mapping.
|
122
|
+
More practically, when developing an API it's likely to
|
123
|
+
be promoted across various environments such as
|
124
|
+
local, qa, stage, and production so having a parameterized root for
|
125
|
+
the URLs eases this while encouraging inter-environment consistency.
|
126
|
+
|
127
|
+
For simple cases where all tests are to be run against the same root,
|
128
|
+
the root url can be specified with the environment variable `ROOT_URL`,
|
129
|
+
such as `ROOT_URL=https://api.myjson.com/ cucumber`, or letting `rake`
|
130
|
+
take care of this for you such as:
|
131
|
+
[source,ruby]
|
132
|
+
----
|
133
|
+
Cucumber::Rake::Task.new do
|
134
|
+
ENV['ROOT_URL'] = 'https://api.myjson.com/'
|
135
|
+
end
|
136
|
+
----
|
137
|
+
which could then be called with `rake cucumber`. The rake approach
|
138
|
+
can be extended for different tasks for each environment, each
|
139
|
+
of which sets the appropriate environment variables allowing the
|
140
|
+
test code itself to follow https://12factor.net/config[Twelve-Factor App guidelines]
|
141
|
+
where Rake provides sugary convenience.
|
142
|
+
|
143
|
+
=== A Basic GET
|
144
|
+
Most tests will involve some form of issuing requests and performing assertions
|
145
|
+
on the responses. Let's start with a simple version of that pattern,
|
146
|
+
testing the response status from a GET request.
|
147
|
+
|
148
|
+
[source,gherkin]
|
149
|
+
----
|
150
|
+
include::../../tutorial/missing.feature[]
|
151
|
+
----
|
152
|
+
|
153
|
+
=== A Write Request
|
154
|
+
For POST, PATCH and PUT requests you'll normally want to include a request body.
|
155
|
+
To support this, additional data can be added to the requests before they are sent
|
156
|
+
(see <<_request_construction>>).
|
157
|
+
|
158
|
+
[source,gherkin]
|
159
|
+
----
|
160
|
+
include::../../tutorial/post_status.feature[]
|
161
|
+
----
|
162
|
+
|
163
|
+
=== Test Response Properties
|
164
|
+
The API that was chosen for testing returns the link to the created resource
|
165
|
+
which is based off of a generated id. That means that the exact response cannot
|
166
|
+
be verified, but instead property based testing can be done to verify that the
|
167
|
+
data is sane and therefore likely trustworthy. In this case we
|
168
|
+
can check that the `uri` response child matches the expected pattern.
|
169
|
+
|
170
|
+
[source,gherkin]
|
171
|
+
----
|
172
|
+
include::../../tutorial/post_matching.feature[]
|
173
|
+
----
|
174
|
+
|
175
|
+
////
|
176
|
+
=== Known Response Data
|
177
|
+
One of the simplest and most obvious things to test for is that the response
|
178
|
+
contains data for which exact values are expected. Continuing from above we
|
179
|
+
can check that the response body returns the fields that we provided.
|
180
|
+
|
181
|
+
[source,gherkin]
|
182
|
+
----
|
183
|
+
include::../../tutorial/post_including.feature[]
|
184
|
+
----
|
185
|
+
////
|
186
|
+
|
187
|
+
== Environment Variables
|
188
|
+
Some Brine behavior can be tuned by passing it appropriate environment variables, listed here.
|
189
|
+
|
190
|
+
`BRINE_LOG_HTTP`::
|
191
|
+
Output HTTP traffic to stdout. Any truthy value will result in request and response
|
192
|
+
metadata being logged, a value of `DEBUG` (case insensitive) will also log the bodies.
|
193
|
+
|
194
|
+
`BRINE_LOG_BINDING`::
|
195
|
+
Log values as they are assigned to variables in Brine steps.
|
196
|
+
|
197
|
+
== Language Conventions
|
198
|
+
=== The use of ``{grave}``s
|
199
|
+
Backticks/grave accents are used as _parameter delimiters_. It is perhaps
|
200
|
+
most helpful to think of them in those explicit terms rather than thinking of them
|
201
|
+
as an alternate _quote_ construct. In particular quoting implies that the parameter
|
202
|
+
value is a string value, while the step transforms allow for alternate data types.
|
203
|
+
|
204
|
+
``{grave}``s were chosen as they are less common than
|
205
|
+
many other syntactical elements and also allow for the use of logically significant
|
206
|
+
quoting within paremeter values while hopefully avoiding the need for escape artistry
|
207
|
+
(as used for argument transforms).
|
208
|
+
|
209
|
+
== Selection and Assertion
|
210
|
+
As tests are generally concerned with performing assertions, a testing DSL should be
|
211
|
+
able to express the variety of assertions that may be needed. Because these are likely
|
212
|
+
to be numerous, it could easily lead to duplicated logic or geometric growth of code due
|
213
|
+
to the combinations of types of assertions and the means to select the inputs for the assertion.
|
214
|
+
|
215
|
+
To avoid this issue the concepts of selection and assertion are considered separate operations in Brine.
|
216
|
+
Internally this corresponds to two steps: the first assigns a selector;
|
217
|
+
the second passes the assertion to that selector which is responsible for applying the assertion against
|
218
|
+
the selected value(s). In standard step use this will still be expressed as a single step,
|
219
|
+
and dynamic step definitions are used to split the work appropriately.
|
220
|
+
|
221
|
+
For example the step:
|
222
|
+
[source,gherkin]
|
223
|
+
----
|
224
|
+
Then the value of the response body is equal to `foo`
|
225
|
+
----
|
226
|
+
Will be split where the subject of the step (`the value of the response body`)
|
227
|
+
defines the selector and the predicate of the step `is equal to {grave}foo{grave}` defines
|
228
|
+
the assertion (which is translated to a step such as `Then it is equal to {grave}foo{grave}`).
|
229
|
+
|
230
|
+
The result of this is that the assertion steps will always follow a pattern where the subject
|
231
|
+
resembles `the value of ...` and the predicate always resembles `is ...`. Learning the selection
|
232
|
+
phrases and the assertion phrases and combining them should be a more efficient and flexible way
|
233
|
+
to become familiar with the language instead of focusing on the resulting combined steps.
|
234
|
+
|
235
|
+
The chosen approach sacrifices eloquence for the sake of consistency.
|
236
|
+
The predicate will always start with `is` which can lead to awkward language such as
|
237
|
+
`is including` rather than simply `includes`.
|
238
|
+
The consistency provides additional benefits such as consistent modification:
|
239
|
+
for instance `is not` can always be use for negation rather than working out the appropriate
|
240
|
+
phrasing for a more natural sounding step (let alone the logic).
|
241
|
+
|
242
|
+
One of the secondary goals of this is that assertion step definitions should very simple to
|
243
|
+
write and modifiers (such as negation) should be provided for free to those definitions.
|
244
|
+
As assertion definitions are likely to be numerous and potentially customized, this should help optimize code economy.
|
245
|
+
|
246
|
+
=== Selection Modifiers
|
247
|
+
To pursue economical flexibility Brine steps attempt to balance step definitions which accommodate variations
|
248
|
+
while keeping the step logic and patterns fairly simple. Selection steps in particular generally accept some
|
249
|
+
parameters that affect their behavior. This allows the relatively small number of selection steps to provide
|
250
|
+
the flexibility to empower the more numerous assertion steps.
|
251
|
+
|
252
|
+
==== Traversal
|
253
|
+
Selection steps can generally target the root of the object specified (such as the response body)
|
254
|
+
or some nodes within the object if it is a non-scalar value (for instance a child of the response body).
|
255
|
+
This is indicated in the <<_selection,step reference selection steps>> by the `[$TRAVERSAL]` placeholder.
|
256
|
+
`child {grave}$EXPRESSION{grave}` or `children {grave}$EXPRESSION{grave}` can optionally be
|
257
|
+
inserted at the placeholder to select nested nodes as described in <<_traversal_2>>.
|
258
|
+
|
259
|
+
==== Negation
|
260
|
+
The selectors also currently handle negation of the associated assertions.
|
261
|
+
This is potentially counter-intuitive but as previously mentioned the intent is that this
|
262
|
+
should ease the creation of assertions. If negation is added to a selector that it is expected that
|
263
|
+
the assertion will _fail_.
|
264
|
+
|
265
|
+
Negation will be normally indicated in the <<_selection,step reference selection steps>> by the presence
|
266
|
+
of the `[not]` placeholder. A similar placeholder may be used that is more readable but leads to an equivalent
|
267
|
+
inversion of the semantics of the statement. To negate the step, the text within the ``[]``s should be inserted
|
268
|
+
in the indicated position.
|
269
|
+
|
270
|
+
[NOTE, caption='Future Versions']
|
271
|
+
Handling this in the selectors is (as mentioned) counter-intuitive and unnecessarily couples the selector
|
272
|
+
to the assertion. It is currently done for practical reasons but is likely to be replaced in a future version
|
273
|
+
after (or as part of) the initial port of the library to another platform. When it is replaced, all existing steps
|
274
|
+
will remain supported through at least one more major revision and most should (most should remain unchanged).
|
275
|
+
|
276
|
+
=== Chained Assertions
|
277
|
+
[WARNING, caption='Unsupported Feature']
|
278
|
+
Use at your own risk, this feature is *not presently supported*.
|
279
|
+
|
280
|
+
For anyone that likes to live on the (relative) edge or if this gathers notable interest...the above also
|
281
|
+
provides an implicit feature: after a value is selected multiple assertions could be performed against it.
|
282
|
+
For instance:
|
283
|
+
|
284
|
+
[source,gherkin]
|
285
|
+
----
|
286
|
+
Then the value of the response body is equal to `foo`
|
287
|
+
And it is of the type `String`
|
288
|
+
----
|
289
|
+
Though this may work in simple cases the present design is likely to produce surprising results since
|
290
|
+
some aspects (such as negation) are handled by the selector so it would be inherited by the conjunctions
|
291
|
+
even though it wouldn't read that way.
|
292
|
+
|
293
|
+
== Traversal
|
294
|
+
The language exposed by Brine is flat but the data returned by the server is likely
|
295
|
+
to include deeper data structures such as objects and collections. To allow selection within
|
296
|
+
such structures a `traversal` language is embedded within some steps which will be indicated
|
297
|
+
by the use of the `TRAVERSAL` placeholder.
|
298
|
+
|
299
|
+
The traversal language consists of a selected subset of http://goessner.net/articles/JsonPath/[JsonPath].
|
300
|
+
|
301
|
+
[NOTE, caption='The Selected Subset']
|
302
|
+
The subset of JsonPath functionality has been chosen that is believed to support all needed
|
303
|
+
test cases without requiring deep familiarity with JsonPath. This may lead to more numerous simple steps
|
304
|
+
in place of fewer steps that use unsupported expressions. Additionally Brine is intended to be
|
305
|
+
ported to a range of platforms and so only those steps outlined here will be supported across those platforms.
|
306
|
+
JsonPath expressions _not_ listed below will not be explicitly disallowed but are not officially supported
|
307
|
+
(will not be tested and will not be ported to another platform if needed).
|
308
|
+
|
309
|
+
=== Cardinality
|
310
|
+
Each traversal expression will select _all_ matching nodes which is therefore represented as a collection.
|
311
|
+
Often, however, only a single node is expected or desired. Therefore the traversal expression will also
|
312
|
+
be accompanied by a phrase which defines the expected cardinality, normally `child` vs. `children`. `children` will
|
313
|
+
_always_ return an array while `child` will return what would be the first element in that array. `child` should be
|
314
|
+
used when accessing a specific node within the tree, while `children` should be used for what amounts to a query
|
315
|
+
across multiple nodes (such as testing the value of a field for every element in a collection).
|
316
|
+
|
317
|
+
=== Expressions
|
318
|
+
`.$KEY`::
|
319
|
+
Access the `KEY` named child of the starting node. The leading `.` can be
|
320
|
+
omitted if at the start of an expression.
|
321
|
+
|
322
|
+
== Resource Cleanup
|
323
|
+
All test suites should clean up after themselves as a matter of hygiene and to help enforce test independence
|
324
|
+
and reproducibility. This is particularly important for this library given that it is likely the systems under test
|
325
|
+
are likely to remain running; accumulated uncleaned resources are at best a nuisance to have to weed through and
|
326
|
+
at worst raise some costs or other due to heightened consumption of assorted resources (as opposed to more
|
327
|
+
ephemeral test environments).
|
328
|
+
|
329
|
+
Brine therefore provides mechanisms to assist in cleaning up those resources which are created as part of a test run.
|
330
|
+
A conceptual hurdle for this type of functionality is that it is very unlikely to be part of the feature that is being
|
331
|
+
specified, and therefore should ideally not be part of the specification. Depending on the functionality
|
332
|
+
(and arguably the https://www.martinfowler.com/articles/richardsonMaturityModel.html[maturity]) of the
|
333
|
+
API, most or all of the cleanup can be automagically done based on convention. There are tentative plans to support
|
334
|
+
multiple techniques for cleaning up resources based on how much can be implicitly ascertained...though presently there
|
335
|
+
exists only one.
|
336
|
+
|
337
|
+
=== Step indicating resource to DELETE
|
338
|
+
If the API supports DELETE requests to remove created resources but it is either desirable or necessary to specify
|
339
|
+
what those resource PATHS are, a step can be used to indicate which resources should be DELETEd upon test completion.
|
340
|
+
|
341
|
+
_see <<_cleanup,Cleanup Step Definitions>>_
|
342
|
+
|
343
|
+
== Step Reference
|
344
|
+
=== Request Construction
|
345
|
+
link:specs.html#_request_construction[icon:cogs[] Specification]
|
346
|
+
|
347
|
+
The requests which are sent as part of a test are constructed using
|
348
|
+
a https://en.wikipedia.org/wiki/Builder_pattern[Builder].
|
349
|
+
|
350
|
+
`When a $METHOD is sent to {grave}$PATH{grave}`::
|
351
|
+
As every request to a REST API is likely to have a significant
|
352
|
+
HTTP `METHOD` and `PATH`, this step is considered required and is therefore used
|
353
|
+
to send the built request. This should therefore be the *last* step for any
|
354
|
+
given request that is being built.
|
355
|
+
|
356
|
+
`When the request body is assigned:`::
|
357
|
+
The multiline content provided will be assigned to the body of the request.
|
358
|
+
This will normally likely be the JSON representation of data.
|
359
|
+
|
360
|
+
`When the request query parameter {grave}$PARAMETER{grave} is assigned {grave}$VALUE{grave}`::
|
361
|
+
Assign `VALUE` to the request query `PARAMETER`.
|
362
|
+
The value will be URL encoded and the key/value pair appended to the URL using
|
363
|
+
the appropriate `?` or `&` delimiter.
|
364
|
+
The order of the parameters in the resulting URL should be considered undefined.
|
365
|
+
|
366
|
+
`When the request header {grave}$HEADER{grave} is assigned {grave}$VALUE{grave}`::
|
367
|
+
Assign `VALUE` to the request header `HEADER`.
|
368
|
+
Will overwrite any earlier value for the specified header, including earlier steps or defaults.
|
369
|
+
|
370
|
+
=== Cleanup
|
371
|
+
`When a resouce is created at {grave}$PATH{grave}`::
|
372
|
+
Mark `PATH` as a resource to DELETE after the test is run. See <<_resource_cleanup>>
|
373
|
+
|
374
|
+
=== Assignment
|
375
|
+
`When {grave}$IDENTIFIER{grave} is assigned {grave}$VALUE{grave}`::
|
376
|
+
Assigns `VALUE` to `IDENTIFIER`.
|
377
|
+
|
378
|
+
`When {grave}$IDENTIFIER{grave} is assigned a random string`::
|
379
|
+
Assigns a random string (UUID) to `IDENTIFIER`.
|
380
|
+
This is particularly useful to assist with test isolation.
|
381
|
+
|
382
|
+
`When {grave}$IDENTIFIER{grave} is assigned a timestamp`::
|
383
|
+
Assigns to `IDENTIFIER` a timestamp value representing the instant at
|
384
|
+
which the step was evaluated.
|
385
|
+
|
386
|
+
=== Selection
|
387
|
+
link:specs.html#_selection[icon:cogs[] Specification]
|
388
|
+
|
389
|
+
_see <<_selection_and_assertion>>_
|
390
|
+
|
391
|
+
`Then the value of the response status is`::
|
392
|
+
Select the status code of the current HTTP response.
|
393
|
+
|
394
|
+
`Then the value of the response body [$TRAVERSAL] is [not]`::
|
395
|
+
Select the value from the body of the response.
|
396
|
+
|
397
|
+
`Then the value of the response body [$TRAVERSAL] does have any element that is [not]`::
|
398
|
+
Select any (at least one) element from the structure within the response body.
|
399
|
+
|
400
|
+
`Then the value of the response body [$TRAVERSAL] has elements which are all`::
|
401
|
+
Select all elements from the structure within the response body.
|
402
|
+
|
403
|
+
=== Assertion
|
404
|
+
link:specs.html#_assertion[icon:cogs[] Specification]
|
405
|
+
|
406
|
+
_see <<_selection_and_assertion>>_
|
407
|
+
|
408
|
+
`Then it is equal to {grave}$VALUE{grave}`::
|
409
|
+
Assert that the current selected value is equivalent to `VALUE`
|
410
|
+
|
411
|
+
`Then it is matching {grave}$VALUE{grave}`::
|
412
|
+
Assert that the current select value matches the regular expression `VALUE`
|
413
|
+
|
414
|
+
`Then it is including {grave}$VALUE{grave}`::
|
415
|
+
Assert that the current select value includes/is a superset of `VALUE`.
|
416
|
+
|
417
|
+
`Then it is a valid {grave}$TYPE{grave}`::
|
418
|
+
Assert that the selected value is a valid instance of a `TYPE`. Presently this
|
419
|
+
is focused on standard data types (intially based on those specified by JSON),
|
420
|
+
but it is designed to handle user specified domain types pending some minor
|
421
|
+
wiring and documentation. The current supported types are:
|
422
|
+
* `Object`: A JSON style object/associative array
|
423
|
+
* `String`
|
424
|
+
* `Number`
|
425
|
+
* `Integer`
|
426
|
+
* `Array`
|
427
|
+
* `Boolean`
|
data/docs/src/index.adoc
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
= Brine
|
2
|
+
Matt Whipple <http://github.com/mwhipple[@mwhipple]>
|
3
|
+
:description: A Cucumber-based DSL for testing REST APIs
|
4
|
+
:keywords: Brine, Cucumber, REST, DSL
|
5
|
+
|
6
|
+
Cucumber DSL for testing REST APIs
|
7
|
+
|
8
|
+
== Documentation
|
9
|
+
The following are the documentation resources presently available.
|
10
|
+
|
11
|
+
link:guide.html[icon:book[] User Guide]::
|
12
|
+
A guide to writing specifications using the Brine provided DSL.
|
13
|
+
link:specs.html[icon:cogs[] Specification]::
|
14
|
+
The Gherkin specification for all of Brine's features.
|
15
|
+
link:cookbook.html[icon:cutlery[] Cookbook]::
|
16
|
+
Solutions to some problems which Brine does not solve directly.
|
data/docs/src/spec.erb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
<%# TODO: Indentation can't be used because of significance to adoc...comment liberally or abuse tags %>
|
2
|
+
<%# TODO: Add a lot of CSS classes so the output can be matched back to this template %>
|
3
|
+
|
4
|
+
=== [feature name]#<%= feature['name'] %>#
|
5
|
+
|
6
|
+
<%if feature.key?('description') %>
|
7
|
+
<%# FIXME: Styling doesn't work here and description seems to contain background %>
|
8
|
+
<%= feature['description'] %>
|
9
|
+
<% end %>
|
10
|
+
|
11
|
+
<%if feature.key?('background') %>
|
12
|
+
==== [background name]#<%= feature['background']['name'] %>#
|
13
|
+
|
14
|
+
<%if feature['background'].key?('description') %>
|
15
|
+
[background description]#<%= feature['background']['description'] %>#
|
16
|
+
<% end %>
|
17
|
+
|
18
|
+
<%if feature['background'].key?('steps') %>
|
19
|
+
[.step-list]
|
20
|
+
<% feature['background']['steps'].each do |step| %>
|
21
|
+
* *<%= step['keyword'].strip %>* <%= step['name'] %>
|
22
|
+
|
23
|
+
<%if step.key?('doc_string') %>
|
24
|
+
[source,gherkin]
|
25
|
+
----
|
26
|
+
<%= step['doc_string']['value'] %>
|
27
|
+
----
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<%if step.key?('rows') %>
|
31
|
+
+<% if step['rows'].first && step['rows'].first.key?('cols') %>
|
32
|
+
[<%= step['rows'].first['cols'] %>]
|
33
|
+
<% end %>
|
34
|
+
|
35
|
+
|====
|
36
|
+
<% step['rows'].each_with_index do |row, index| %>
|
37
|
+
<% row['cells'].each_with_index do |cell, i| %>
|
38
|
+
<% if row.key?('cell-styles') && row['cell-styles'].length > i %>
|
39
|
+
<%= row['cell-styles'][i] %>
|
40
|
+
<% end %>
|
41
|
+
| <%= cell %>
|
42
|
+
<% end %>
|
43
|
+
<% end %>|====
|
44
|
+
<% end %>
|
45
|
+
<% end %>
|
46
|
+
<% end %>
|
47
|
+
|
48
|
+
<%if feature['background'].key?('examples')
|
49
|
+
example = feature['background']['examples'] %>
|
50
|
+
|
51
|
+
==== EX: <%= example['keyword'].strip %> <%= example['name'] %>
|
52
|
+
|
53
|
+
<% if example['rows'].first && example['rows'].first.key?('cols') %>
|
54
|
+
[<%= example['rows'].first['cols'] %>]
|
55
|
+
<% end %>
|
56
|
+
|
57
|
+
|====
|
58
|
+
<% example['rows'].each_with_index do |row, index| %>
|
59
|
+
<% row['cells'].each_with_index do |cell, i| %>
|
60
|
+
<% if row.key?('cell-styles') && row['cell-styles'].length > i %>
|
61
|
+
<%= row['cell-styles'][i] %>
|
62
|
+
<% end %>
|
63
|
+
| <%= cell %>
|
64
|
+
<% end %>
|
65
|
+
<% if index == 0 %>
|
66
|
+
<% end %>
|
67
|
+
<% end %>
|
68
|
+
|====
|
69
|
+
<% end %>
|
70
|
+
<% end %>
|
71
|
+
|
72
|
+
<%if feature.key?('scenarios') %>
|
73
|
+
<% feature['scenarios'].each do |scenario| %>
|
74
|
+
==== [scenario name]#<%= scenario['name'] %>#
|
75
|
+
<%if scenario.key?('description') %>
|
76
|
+
<%= scenario['description'] %>
|
77
|
+
<% end %>
|
78
|
+
<%if scenario.key?('steps') %>[.step-list]<% scenario['steps'].each do |step| %>
|
79
|
+
* *<%= step['keyword'].strip %>* <%= step['name'] %>
|
80
|
+
<%if step.key?('doc_string') %>
|
81
|
+
[source,gherkin]
|
82
|
+
----
|
83
|
+
<%= step['doc_string']['value'] %>
|
84
|
+
----
|
85
|
+
<% end %>
|
86
|
+
<%if step.key?('rows') %>
|
87
|
+
+<% if step['rows'].first && step['rows'].first.key?('cols') %>
|
88
|
+
[<%= step['rows'].first['cols'] %>]
|
89
|
+
<% end %>
|
90
|
+
|====
|
91
|
+
<% step['rows'].each_with_index do |row, index| %>
|
92
|
+
<% row['cells'].each_with_index do |cell, i| %>
|
93
|
+
<% if row.key?('cell-styles') && row['cell-styles'].length > i %>
|
94
|
+
<%= row['cell-styles'][i] %>
|
95
|
+
<% end %>
|
96
|
+
| <%= cell %>
|
97
|
+
<% end %>
|
98
|
+
<% end %>|====
|
99
|
+
<% end %>
|
100
|
+
<% end %>
|
101
|
+
<% end %>
|
102
|
+
|
103
|
+
<%if scenario.key?('examples')
|
104
|
+
example = scenario['examples'] %>
|
105
|
+
===== [example name]#<%= example['keyword'].strip %> <%= example['name'] %>#
|
106
|
+
|
107
|
+
<% if example['rows'].first && example['rows'].first.key?('cols') %>
|
108
|
+
[<%= example['rows'].first['cols'] %>]
|
109
|
+
<% end %>
|
110
|
+
|====
|
111
|
+
<% example['rows'].each_with_index do |row, index| %>
|
112
|
+
<% row['cells'].each_with_index do |cell, i| %>
|
113
|
+
<% if row.key?('cell-styles') && row['cell-styles'].length > i %><%= row['cell-styles'][i] %><% end %>
|
114
|
+
| <%= cell %>
|
115
|
+
<% end %> <%# cell %>
|
116
|
+
<% if index == 0 %>
|
117
|
+
<% end %>
|
118
|
+
<% end %>|====
|
119
|
+
<% end %>
|
120
|
+
<% end %>
|
121
|
+
<% end %>
|
data/docs/src/specs.adoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= icon:cogs[] Brine Specifications
|
2
|
+
Matt Whipple <http://github.com/mwhipple[@mwhipple]>
|
3
|
+
:description: Specifications for Brine
|
4
|
+
:keywords: Brine, Cucumber, RESt, DSL
|
5
|
+
|
6
|
+
== Request Construction
|
7
|
+
gherkin::../../features/request_construction/basic.feature[spec.erb]
|
8
|
+
gherkin::../../features/request_construction/body.feature[spec.erb]
|
9
|
+
gherkin::../../features/request_construction/params.feature[spec.erb]
|
10
|
+
gherkin::../../features/request_construction/headers.feature[spec.erb]
|
11
|
+
gherkin::../../features/request_construction/clearing.feature[spec.erb]
|
12
|
+
|
13
|
+
== Resource Cleanup
|
14
|
+
gherkin::../../features/resource_cleanup/cleanup.feature[spec.erb]
|
15
|
+
|
16
|
+
== Selection
|
17
|
+
gherkin::../../features/selectors/any.feature[spec.erb]
|
18
|
+
gherkin::../../features/selectors/all.feature[spec.erb]
|
19
|
+
|
20
|
+
== Assertion
|
21
|
+
gherkin::../../features/assertions/is_equal_to.feature[spec.erb]
|
22
|
+
gherkin::../../features/assertions/is_matching.feature[spec.erb]
|
23
|
+
gherkin::../../features/assertions/is_including.feature[spec.erb]
|
24
|
+
gherkin::../../features/assertions/is_a_valid.feature[spec.erb]
|