UNPKG

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