1 | ---
|
2 | title: The Kettle Testing Framework
|
3 | layout: default
|
4 | category: Kettle
|
5 | ---
|
6 |
|
7 | The Kettle testing framework, which can be used for issuing test fixtures against arbitrary HTTP and WebSockets servers, does not depend on
|
8 | the rest of Kettle, but is bundled along with it. To get access to the testing framework, after
|
9 |
|
10 | var kettle = require("kettle");
|
11 |
|
12 | then issue
|
13 |
|
14 | kettle.loadTestingSupport();
|
15 |
|
16 | Note that any users of the Kettle Testing Framework need to list the [node-jqunit](https://www.npmjs.com/package/node-jqunit) module in the `devDependencies` section of their own `package.json`.
|
17 |
|
18 | The Kettle testing framework flattens out what would be complex callback or promise-laden code into a declarative array of JSON records, each encoding
|
19 | a successive stage in the HTTP or WebSockets conversation. The Kettle testing framework makes use of
|
20 | Infusion's [IoC Testing Framework](http://docs.fluidproject.org/infusion/development/IoCTestingFramework.html) to encode the test fixtures – you should be familiar with this framework
|
21 | as well as with the use of Infusion IoC in general before using it.
|
22 |
|
23 | The standard use of the Kettle testing framework involves assembling arrays with alternating active and passive elements using the methods of the
|
24 | testing request fixture components `kettle.test.request.http` and `kettle.test.request.ws`. The active records will use the `send` method of `kettle.test.request.http`
|
25 | (or one of the event firing methods of `kettle.test.request.ws`) to send a request to the server under test, and the passive records will contain a `listener` element
|
26 | in order to listen to the response from the server and verify that it has a particular form.
|
27 |
|
28 | ## A simple Kettle testing framework example
|
29 |
|
30 | Before documenting the Kettle testing framework request grades `kettle.test.request.http` and `kettle.test.request.ws` in detail, we'll construct a simple example,
|
31 | testing the simple example application which we developed in the section describing [kettle applications](ConfigsAndApplications.md#a-simple-kettle-application).
|
32 |
|
33 | ```javascript
|
34 | kettle.loadTestingSupport();
|
35 |
|
36 | fluid.registerNamespace("examples.tests.simpleConfig");
|
37 |
|
38 | examples.tests.simpleConfig.testDefs = [{
|
39 | name: "SimpleConfig GET test",
|
40 | expect: 2,
|
41 | config: {
|
42 | configName: "examples.simpleConfig",
|
43 | configPath: "%kettle/examples/simpleConfig"
|
44 | },
|
45 | components: {
|
46 | getRequest: {
|
47 | type: "kettle.test.request.http",
|
48 | options: {
|
49 | path: "/handlerPath",
|
50 | method: "GET"
|
51 | }
|
52 | }
|
53 | },
|
54 | sequence: [{
|
55 | func: "{getRequest}.send"
|
56 | }, {
|
57 | event: "{getRequest}.events.onComplete",
|
58 | listener: "kettle.test.assertJSONResponse",
|
59 | args: {
|
60 | message: "Received GET request from simpleConfig server",
|
61 | string: "{arguments}.0",
|
62 | request: "{getRequest}",
|
63 | expected: {
|
64 | message: "GET request received on path /handlerPath"
|
65 | }
|
66 | }
|
67 | }]
|
68 | }];
|
69 |
|
70 | kettle.test.bootstrapServer(examples.tests.simpleConfig.testDefs);
|
71 | ```
|
72 |
|
73 | You can run a live version of this sample by running
|
74 |
|
75 | node testSimpleConfig.js
|
76 |
|
77 | from the [examples/testingSimpleConfig](../examples/testingSimpleConfig) directory of this project.
|
78 |
|
79 | This sample sets up JSON configuration to load the `examples.simpleConfig` application from this module's `examples` directory, and then
|
80 | defines a single request test component, named `getRequest`, of type `kettle.test.request.http` which targets its path. The `sequence` section
|
81 | of the configuration then consists of two elements – the first sends the request, and the second listens for the `onComplete` event fired by
|
82 | the request and verifies that the returned payload is exactly as expected.
|
83 |
|
84 | Note the use of two particular pieces of Kettle's infrastructure – firstly the use of module-relative paths, where we use the contextualised
|
85 | reference `%kettle` in order to resolve a file path relative to the base directory of this module, and secondly the Kettle testing assert function
|
86 | [`kettle.test.assertJSONResponse`](#helper-methods-for-making-assertions-on-oncomplete), which is a helpful all-in-one utility for verifying an HTTP response status code as well as response payload.
|
87 |
|
88 | <a id="#kettle.test.request.http"></a>
|
89 |
|
90 | ### Configuration options available on `kettle.test.request.http`
|
91 |
|
92 | To get a sense of the capabilities of a `kettle.test.request.http`, you should browse node.js's documentation for its [`http.request`](https://nodejs.org/api/http.html#http_http_request_options_callback),
|
93 | for which this component is a wrapper. A `kettle.test.request.http` component accepts a number of options configuring its function:
|
94 |
|
95 |
|
96 | <table>
|
97 | <thead>
|
98 | <tr>
|
99 | <th colspan="3">Supported configurable options for a <code>kettle.test.request.http</code></th>
|
100 | </tr>
|
101 | <tr>
|
102 | <th>Option</th>
|
103 | <th>Type</th>
|
104 | <th>Description</th>
|
105 | </tr>
|
106 | </thead>
|
107 | <tbody>
|
108 | <tr>
|
109 | <td><code>path</code></td>
|
110 | <td><code>String</code> (default: <code>/</code>)</td>
|
111 | <td>The HTTP path to which this request is to be made</td>
|
112 | </tr>
|
113 | <tr>
|
114 | <td><code>method</code></td>
|
115 | <td><code>String</code> (default: <code>GET</code>)</td>
|
116 | <td>The HTTP method to be used to send the request</td>
|
117 | </tr>
|
118 | <tr>
|
119 | <td><code>port</code></td>
|
120 | <td><code>Number</code> (default: 8081)</td>
|
121 | <td>The port number on the server for this request to connect to</td>
|
122 | </tr>
|
123 | <tr>
|
124 | <td><code>termMap</code></td>
|
125 | <td><code>Object</code> (map of <code>String</code> to <code>String</code>)</td>
|
126 | <td>The keys of this map are interpolated terms within the <code>path</code> option (considered as a template where these keys will be prefixed by <code>%</code>). The values will be interpolated directly
|
127 | into the path. This structure will be merged with the option of the same name held in the <code>directOptions</code> argument to the request component's <code>send</code> method.</td>
|
128 | </tr>
|
129 | <tr>
|
130 | <td><code>headers</code></td>
|
131 | <td><code>Object</code></td>
|
132 | <td>The HTTP headers to be sent with the request</td>
|
133 | </tr>
|
134 | </tbody>
|
135 | </table>
|
136 |
|
137 | In addition, the `kettle.test.request.http` component will accept any options accepted by node's native [`http.request`](https://nodejs.org/api/http.html#http_http_request_options_callback) constructor –
|
138 | supported in addition to the above are `host`, `hostname`, `family`, `localAddress`, `socketPath`, `auth` and `agent`. All of these options will be overriden by options of the same names supplied as the <code>directOptions</code>
|
139 | argument to the component's `send` method, described in the following section:
|
140 |
|
141 | ### Using a `kettle.test.request.http` – the `send` method
|
142 |
|
143 | The primarily useful method on `kettle.test.request.http` is `send`. It accepts two arguments, `(model, directOptions)` :
|
144 |
|
145 | <table>
|
146 | <thead>
|
147 | <tr>
|
148 | <th colspan="3">Arguments accepted by <code>send</code> method of <code>kettle.test.request.http</code></th>
|
149 | </tr>
|
150 | <tr>
|
151 | <th>Option</th>
|
152 | <th>Type</th>
|
153 | <th>Description</th>
|
154 | </tr>
|
155 | </thead>
|
156 | <tbody>
|
157 | <tr>
|
158 | <td><code>model</code></td>
|
159 | <td><code>Object/String</code> (optional)</td>
|
160 | <td>If the HTTP method selected is one accepting a payload (PUT/POST), the payload to be sent by this HTTP request. If this is an <code>Object</code> it will be stringified as JSON, and if it is
|
161 | a <code>String</code> it will be sent as the request body directly.</td>
|
162 | </tr>
|
163 | <tr>
|
164 | <td><code>directOptions</code></td>
|
165 | <td><code>Object</code> (optional)</td>
|
166 | <td>A set of extra options governing processing of this request. This will be merged with options taken from the component options supplied to the `kettle.test.request.http` component in order
|
167 | to arrive at a merged set of per-request options. All of the options described in the previous table are supported here. In particular, entries in <code>headers</code> will be filled in by the implementation –
|
168 | the header <code>Content-Length</code> will be populated automatically based on the supplied <code>model</code> to the <code>send</code> method,
|
169 | and the header <code>Content-Type</code> will default to <code>application/json</code> if no value is supplied</td>
|
170 | </tr>
|
171 | </tbody>
|
172 | </table>
|
173 |
|
174 | ### Listening for a response from `kettle.test.request.http` – the `onComplete` event
|
175 |
|
176 | The response to a `send` request will be notified to listeners of the component's `onComplete` event. Note that since a given `kettle.test.request.http` request component
|
177 | can be used to send at most ***one*** request, there can be no confusion about which response is associated which which request. The `onComplete` event fires with the signature `(data, that, parsedData)`,
|
178 | which are described in the following table:
|
179 |
|
180 | <table>
|
181 | <thead>
|
182 | <tr>
|
183 | <th colspan="3">Arguments fired by the <code>onComplete</code> event of <code>kettle.test.request.http</code></th>
|
184 | </tr>
|
185 | <tr>
|
186 | <th>Option</th>
|
187 | <th>Type</th>
|
188 | <th>Description</th>
|
189 | </tr>
|
190 | </thead>
|
191 | <tbody>
|
192 | <tr>
|
193 | <td><code>data</code></td>
|
194 | <td><code>String</code></td>
|
195 | <td>The request body received from the HTTP request</td>
|
196 | </tr>
|
197 | <tr>
|
198 | <td><code>that</code></td>
|
199 | <td><code>Component</code></td>
|
200 | <td>The <code>kettle.test.request.http</code> component itself. <it><strong>Note</strong></it>: By the time this event fires, this component will have a member <code>nativeResponse</code> assigned, of type
|
201 | <a href="https://nodejs.org/api/http.html#http_http_incomingmessage">http.IncomingMessage</a> – this object can be used to read off various standard pieces of the response to node.js's <code>http.ClientRequest</code>,
|
202 | including the HTTP <code>statusCode</code>, headers, etc.</td>
|
203 | </tr>
|
204 | <tr>
|
205 | <td><code>parsedData</code></td>
|
206 | <td><code>Object</code></td>
|
207 | <td>This final argument includes various pieces of special information parsed out of the server's response. Currently it contains only two members, `cookies` and `signedCookies`. The former simply contains
|
208 | the value of any standard header returned as <code>set-cookie</code> The latter is populated if a <code>cookieJar</code> is configured in this component's tree which is capable of parsing cookies encrypted
|
209 | with a "shared secret". Consult ths section on use of <a href="#using-cookies-with-an-http-testing-request">cookies</code></a> for more information.</td>
|
210 | </tr>
|
211 | </tbody>
|
212 | </table>
|
213 |
|
214 | ### Helper methods for making assertions on onComplete
|
215 |
|
216 | The Kettle testing framework includes two helper functions to simplify the process of making assertions on receiving the `onComplete` event of a `kettle.test.request.http` component. These are
|
217 | named `kettle.test.assertJSONResponse`, which asserts that a successful HTTP response has been received with a particular JSON payload, and `kettle.test.assertErrorResponse`, which asserts
|
218 | that an HTTP response was received, whilst checking for various details in the message. Both of these helper functions accept a single complex `options` object encoding all of their requirements,
|
219 | which are documented in the following tables:
|
220 |
|
221 | <table>
|
222 | <thead>
|
223 | <tr>
|
224 | <th colspan="3">Options accepted by the <code>kettle.test.assertJSONResponse</code> helper function</th>
|
225 | </tr>
|
226 | <tr>
|
227 | <th>Option</th>
|
228 | <th>Type</th>
|
229 | <th>Description</th>
|
230 | </tr>
|
231 | </thead>
|
232 | <tbody>
|
233 | <tr>
|
234 | <td><code>string</code></td>
|
235 | <td><code>String</code></td>
|
236 | <td>The returned request body from the HTTP request</td>
|
237 | </tr>
|
238 | <tr>
|
239 | <td><code>request</code></td>
|
240 | <td><code>Component</code></td>
|
241 | <td>The <code>kettle.test.request.http</code> component which fired the request whose response is being tested</td>
|
242 | </tr>
|
243 | <tr>
|
244 | <td><code>statusCode</code></td>
|
245 | <td><code>Number</code> (default: 200)</td>
|
246 | <td>The expected HTTP status code in ther response</td>
|
247 | </tr>
|
248 | <tr>
|
249 | <td><code>expected</code></td>
|
250 | <td><code>Object</code></td>
|
251 | <td>The expected response payload, encoded as an <code>Object</code> – comparison will be made using a deep equality algorithm (<code>jqUnit.assertDeepEq</code>)</td>
|
252 | </tr>
|
253 | </tbody>
|
254 | </table>
|
255 |
|
256 | <code>kettle.test.assertErrorResponse</code> will expect that the returned HTTP response body will parse as a JSON structure.
|
257 | In addition to the checks described in this table, <code>kettle.test.assertErrorResponse</code> will also assert that the returned payload has an `isError` member set to `true`:
|
258 |
|
259 | <table>
|
260 | <thead>
|
261 | <tr>
|
262 | <th colspan="3">Options accepted by the <code>kettle.test.assertErrorResponse</code> helper function</th>
|
263 | </tr>
|
264 | <tr>
|
265 | <th>Option</th>
|
266 | <th>Type</th>
|
267 | <th>Description</th>
|
268 | </tr>
|
269 | </thead>
|
270 | <tbody>
|
271 | <tr>
|
272 | <td><code>string</code></td>
|
273 | <td><code>String</code></td>
|
274 | <td>The returned request body from the HTTP request</td>
|
275 | </tr>
|
276 | <tr>
|
277 | <td><code>request</code></td>
|
278 | <td><code>Component</code></td>
|
279 | <td>The <code>kettle.test.request.http</code> component which fired the request whose response is being tested</td>
|
280 | </tr>
|
281 | <tr>
|
282 | <td><code>statusCode</code></td>
|
283 | <td><code>Number</code> (default: 500)</td>
|
284 | <td>The expected HTTP status code in ther response</td>
|
285 | </tr>
|
286 | <tr>
|
287 | <td><code>errorTexts</code></td>
|
288 | <td><code>String/Array of String</code></td>
|
289 | <td>A single <code>String</code> or array of <code>String</code>s which must appear at some index within the <code>message</code> field of the returned JSON response payload</td>
|
290 | </tr>
|
291 | </tbody>
|
292 | </table>
|
293 |
|
294 | ### Using cookies with an HTTP testing request
|
295 |
|
296 | A framework grade, `kettle.test.request.httpCookie`, derived from `kettle.test.request.http`, will cooperate with a component of type `kettle.test.cookieJar` which must be
|
297 | configured higher in the component tree in order to store and parse cookies. The `kettle.test.cookieJar` is automatically configured as a child of the overall `fluid.test.testCaseHolder`, but
|
298 | unless the `kettle.test.request.httpCookie` grade is used for the testing request, any returned cookies will be ignored. The `fluid.test.testCaseHolder` accepts an option, <code>secret</code>, which is
|
299 | broadcast both to the server and to the cookieJar (using Infusion's [distributeOptions](http://docs.fluidproject.org/infusion/development/IoCSS.html) directive) in order to enable them to cooperate on transmitting signed cookies.
|
300 | Consult the framework tests at [tests/shared/SessionTestDefs.js](../tests/shared/SessionTestDefs.js) for examples of how to write a sequence of HTTP fixtures enrolled in a session by means of returned cookies, both signed and unsigned.
|
301 |
|
302 | These tests are also a good example of configuring custom [middleware](#working-with-middleware) into the middle of a request's middleware chain. These tests include a middleware grade named `kettle.tests.middleware.validateSession` which
|
303 | will reject requests without a particular piece of populated session data, before processing reaches the request's `requestHandler`.
|
304 |
|
305 | ### Issuing WebSockets testing fixtures with `kettle.test.request.ws`
|
306 |
|
307 | A sibling grade of `kettle.test.request.http` is `kettle.test.request.ws` which will allow the testing of WebSockets endpoints in an analogous way. You should browse the `ws` project's documentation for
|
308 | [ws.WebSocket](https://github.com/websockets/ws/blob/master/doc/ws.md#new-wswebsocketaddress-protocols-options) for which `kettle.test.request.ws` is a wrapper. As with `kettle.test.request.http`, messages are sent
|
309 | using an invoker named `send`, with the difference that method may be invoked any number of times. The options for `kettle.test.request.ws` are as follows:
|
310 |
|
311 | <table>
|
312 | <thead>
|
313 | <tr>
|
314 | <th colspan="3">Supported configurable options for a <code>kettle.test.request.ws</code></th>
|
315 | </tr>
|
316 | <tr>
|
317 | <th>Option</th>
|
318 | <th>Type</th>
|
319 | <th>Description</th>
|
320 | </tr>
|
321 | </thead>
|
322 | <tbody>
|
323 | <tr>
|
324 | <td><code>path</code></td>
|
325 | <td><code>String</code> (default: <code>/</code>)</td>
|
326 | <td>The HTTP path to which this request is to be made</td>
|
327 | </tr>
|
328 | <tr>
|
329 | <td><code>port</code></td>
|
330 | <td><code>Number</code> (default: 8081)</td>
|
331 | <td>The port number on the server for this request to connect to</td>
|
332 | </tr>
|
333 | <tr>
|
334 | <td><code>sendJSON</code></td>
|
335 | <td><code>Boolean</code> (default: <code>true</code>)</td>
|
336 | <td>If this is set to <code>true</code>, the argument fired to the component's <code>send</code> method will be encoded as JSON. Otherwise the argument will be sent to <code>websocket.send</code> as is.</td>
|
337 | </tr>
|
338 | <tr>
|
339 | <td><code>receiveJSON</code></td>
|
340 | <td><code>Boolean</code> (default: <code>true</code>)</td>
|
341 | <td>If this is set to <code>true</code>, the argument received by listeners to the component's <code>onReceiveMessage</code> event will be encoded as JSON. Otherwise the value will be transmitted as from the WebSocket's <code>message</code> event unchanged.</td>
|
342 | </tr>
|
343 | <tr>
|
344 | <td><code>webSocketsProtocols</code></td>
|
345 | <td><code>String/Array</code></td>
|
346 | <td>Forwarded to the <code>protocols</code> constructor argument of <a href="https://github.com/websockets/ws/blob/master/doc/ws.md#new-wswebsocketaddress-protocols-options"><code>ws.WebSocket</code></a></td>
|
347 | </tr>
|
348 | <tr>
|
349 | <td><code>termMap</code></td>
|
350 | <td><code>Object</code> (map of <code>String</code> to <code>String</code>)</td>
|
351 | <td>The keys of this map are interpolated terms within the <code>path</code> option (considered as a template where these keys will be prefixed by <code>%</code>). The values will be interpolated directly
|
352 | into the path. This structure will be merged with the option of the same name held in the <code>directOptions</code> argument to the request component's <code>send</code> method.</td>
|
353 | </tr>
|
354 | <tr>
|
355 | <td><code>headers</code></td>
|
356 | <td><code>Object</code></td>
|
357 | <td>The HTTP headers to be sent with the request</td>
|
358 | </tbody>
|
359 | </table>
|
360 |
|
361 | In addition to the above options, any option may be supplied that is supported by the `options` argument of [ws.WebSocket](https://github.com/websockets/ws/blob/master/doc/ws.md#new-wswebsocketaddress-protocols-options).
|
362 | These include, in addition to the above, `protocol`, `agent`, `protocolVersion`, `hostname`.
|
363 |
|
364 | ### Events attached to a `kettle.test.request.ws`
|
365 |
|
366 | The following events may be listened to on a `kettle.test.request.ws` component:
|
367 |
|
368 | <table>
|
369 | <thead>
|
370 | <tr>
|
371 | <th colspan="3">Events attached to a <code>kettle.test.request.ws</code></th>
|
372 | </tr>
|
373 | <tr>
|
374 | <th>Event name</th>
|
375 | <th>Arguments</th>
|
376 | <th>Description</th>
|
377 | </tr>
|
378 | </thead>
|
379 | <tbody>
|
380 | <tr>
|
381 | <td><code>onConnect</code></td>
|
382 | <td><code>(that: Component)</code></td>
|
383 | <td>Fired when the <code>open</code> event of the underlying <code>ws.WebSocket</code> is fired. This event must be listened to in the fixture sequence before any attempt is made to fire messages from the
|
384 | component with <code>send</code></td>
|
385 | </tr>
|
386 | <tr>
|
387 | <td><code>onError</code></td>
|
388 | <td><code>(error: Object, that: Component, res: <a href="https://nodejs.org/api/http.html#http_http_incomingmessage">http.IncomingMessage</a>)</td>
|
389 | <td>Fired either if an error occurs during the HTTP upgrade process, or if an <code>error</code> event is fired from the <code>ws.WebSocket</code> object once the socket is established. For an error during
|
390 | handshake, the <code>error</code> argument will be an object with <code>isError: true</code> and a <code>statusCode</code> field taken from the HTTP statusCode. For an <code>error</code> event, the
|
391 | error will be the original error payload.</td>
|
392 | </tr>
|
393 | <tr>
|
394 | <td><code>onReceiveMessage</code></td>
|
395 | <td><code>(data: String/Object, that: Component)</td>
|
396 | <td>Fired whenever the underlying <code>ws.WebSocket</code> receives an <code>message</code> event. If the <code>receiveJSON</code> option to the component is <code>true</code> this value will have been
|
397 | JSON decoded.
|
398 | </tr>
|
399 | </tbody>
|
400 | </table>
|
401 |
|
402 | ### Connecting a `kettle.test.request.ws` client with the `connect` method
|
403 |
|
404 | Before a `kettle.test.request.ws` component can be used to send messages, it must be connected by calling the `connect` method. This method accepts no arguments. When the client has been connected to the server,
|
405 | the `onConnect` event documented in the above table will be fired on the component.
|
406 |
|
407 | ### Sending a message using the `send` method of `kettle.test.request.ws`
|
408 |
|
409 | The signature of `kettle.test.request.ws` `send` is the same as that for `kettle.test.request.http`, with a very similar meaning:
|
410 |
|
411 | The primarily useful method on `kettle.test.request.ws` is `send`. It accepts two arguments, `(model, directOptions)` :
|
412 |
|
413 | <table>
|
414 | <thead>
|
415 | <tr>
|
416 | <th colspan="3">Arguments accepted by <code>send</code> method of <code>kettle.test.request.ws</code></th>
|
417 | </tr>
|
418 | <tr>
|
419 | <th>Option</th>
|
420 | <th>Type</th>
|
421 | <th>Description</th>
|
422 | </tr>
|
423 | </thead>
|
424 | <tbody>
|
425 | <tr>
|
426 | <td><code>model</code></td>
|
427 | <td><code>Object/String</code></td>
|
428 | <td>The payload to be sent with the underlying <code>ws.WebSocket.send</code> call. If the component's <code>sendJSON</code> option is set to <code>true</code> (the default), an Object sent here will be
|
429 | automatically JSON-encoded.</td>
|
430 | </tr>
|
431 | <tr>
|
432 | <td><code>directOptions</code></td>
|
433 | <td><code>Object</code> (optional)</td>
|
434 | <td>These options will be sent as the 2nd argument of <a href="https://github.com/websockets/ws/blob/master/doc/ws.md#websocketsenddata-options-callback"><code>ws.WebSocket.send</code></a></td>
|
435 | </tr>
|
436 | </tbody>
|
437 | </table>
|
438 |
|
439 | ### Issuing session-aware WebSockets requests
|
440 |
|
441 | Analogous with `kettle.test.request.http.cookie`, there is a session-aware variant of the request grade `kettle.test.request.ws`, named `kettle.test.request.ws.cookie`. Its behaviour is identical
|
442 | with that of `kettle.test.request.http.cookie`, in particular being able to share access to the same `kettle.test.cookieJar` component to enable a mixed series of HTTP and WebSockets requests
|
443 | to be contextualised by the same session cookies.
|
444 |
|
445 | ## Framework tests
|
446 |
|
447 | Please consult the [test cases](../tests) for the framework for more examples of Kettle primitives as well as the Kettle testing framework in action.
|