UNPKG

23.9 kBMarkdownView Raw
1---
2title: The Kettle Testing Framework
3layout: default
4category: Kettle
5---
6
7The Kettle testing framework, which can be used for issuing test fixtures against arbitrary HTTP and WebSockets servers, does not depend on
8the rest of Kettle, but is bundled along with it. To get access to the testing framework, after
9
10 var kettle = require("kettle");
11
12then issue
13
14 kettle.loadTestingSupport();
15
16Note 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
18The Kettle testing framework flattens out what would be complex callback or promise-laden code into a declarative array of JSON records, each encoding
19a successive stage in the HTTP or WebSockets conversation. The Kettle testing framework makes use of
20Infusion's [IoC Testing Framework](http://docs.fluidproject.org/infusion/development/IoCTestingFramework.html) to encode the test fixtures – you should be familiar with this framework
21as well as with the use of Infusion IoC in general before using it.
22
23The standard use of the Kettle testing framework involves assembling arrays with alternating active and passive elements using the methods of the
24testing 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
26in 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
30Before documenting the Kettle testing framework request grades `kettle.test.request.http` and `kettle.test.request.ws` in detail, we'll construct a simple example,
31testing the simple example application which we developed in the section describing [kettle applications](ConfigsAndApplications.md#a-simple-kettle-application).
32
33```javascript
34kettle.loadTestingSupport();
35
36fluid.registerNamespace("examples.tests.simpleConfig");
37
38examples.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
70kettle.test.bootstrapServer(examples.tests.simpleConfig.testDefs);
71```
72
73You can run a live version of this sample by running
74
75 node testSimpleConfig.js
76
77from the [examples/testingSimpleConfig](../examples/testingSimpleConfig) directory of this project.
78
79This sample sets up JSON configuration to load the `examples.simpleConfig` application from this module's `examples` directory, and then
80defines a single request test component, named `getRequest`, of type `kettle.test.request.http` which targets its path. The `sequence` section
81of the configuration then consists of two elements – the first sends the request, and the second listens for the `onComplete` event fired by
82the request and verifies that the returned payload is exactly as expected.
83
84Note the use of two particular pieces of Kettle's infrastructure – firstly the use of module-relative paths, where we use the contextualised
85reference `%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
92To 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),
93for 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
137In 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 –
138supported 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>
139argument to the component's `send` method, described in the following section:
140
141### Using a `kettle.test.request.http` – the `send` method
142
143The 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
176The 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
177can 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)`,
178which 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
216The 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
217named `kettle.test.assertJSONResponse`, which asserts that a successful HTTP response has been received with a particular JSON payload, and `kettle.test.assertErrorResponse`, which asserts
218that 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,
219which 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.
257In 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
296A 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
297configured 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
298unless 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
299broadcast 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.
300Consult 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
302These 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
303will 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
307A 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
309using 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
361In 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).
362These include, in addition to the above, `protocol`, `agent`, `protocolVersion`, `hostname`.
363
364### Events attached to a `kettle.test.request.ws`
365
366The 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
404Before 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,
405the `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
409The signature of `kettle.test.request.ws` `send` is the same as that for `kettle.test.request.http`, with a very similar meaning:
410
411The 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
441Analogous 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
442with 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
443to be contextualised by the same session cookies.
444
445## Framework tests
446
447Please consult the [test cases](../tests) for the framework for more examples of Kettle primitives as well as the Kettle testing framework in action.