title | layout | category |
---|---|---|
The Kettle Testing Framework |
default |
Kettle |
The Kettle testing framework, which can be used for issuing test fixtures against arbitrary HTTP and WebSockets servers, does not depend on the rest of Kettle, but is bundled along with it. To get access to the testing framework, after
var kettle = require("kettle");
then issue
kettle.loadTestingSupport();
Note that any users of the Kettle Testing Framework need to list the
node-jqunit module in the devDependencies
section of their own
package.json
.
The Kettle testing framework flattens out what would be complex callback or promise-laden code into a declarative array of JSON records, each encoding a successive stage in the HTTP or WebSockets conversation. The Kettle testing framework makes use of Infusion's IoC Testing Framework to encode the test fixtures – you should be familiar with this framework as well as with the use of Infusion IoC in general before using it.
The standard use of the Kettle testing framework involves assembling arrays with alternating active and passive
elements using the methods of the 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
(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 in order to listen to the response from the server and verify that it
has a particular form.
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, testing the simple example application which we developed in the section
describing kettle applications.
kettle.loadTestingSupport();
fluid.registerNamespace("examples.tests.simpleConfig");
examples.tests.simpleConfig.testDefs = [{
name: "SimpleConfig GET test",
expect: 2,
config: {
configName: "examples.simpleConfig",
configPath: "%kettle/examples/simpleConfig"
},
components: {
getRequest: {
type: "kettle.test.request.http",
options: {
path: "/handlerPath",
method: "GET"
}
}
},
sequence: [{
func: "{getRequest}.send"
}, {
event: "{getRequest}.events.onComplete",
listener: "kettle.test.assertJSONResponse",
args: {
message: "Received GET request from simpleConfig server",
string: "{arguments}.0",
request: "{getRequest}",
expected: {
message: "GET request received on path /handlerPath"
}
}
}]
}];
kettle.test.bootstrapServer(examples.tests.simpleConfig.testDefs);
You can run a live version of this sample by running
node testSimpleConfig.js
from the examples/testingSimpleConfig directory of this project.
This sample sets up JSON configuration to load the examples.simpleConfig
application from this module's examples
directory, and then defines a single request test component, named getRequest
, of type kettle.test.request.http
which targets its path. It also sets the expected number of successful assertions by specifying expect: 2
.
The sequence
section of the configuration then consists of two elements – the first sends the
request, and the second listens for the onComplete
event fired by the request and verifies that the returned payload
is exactly as expected.
Note the use of two particular pieces of Kettle's infrastructure – firstly the use of module-relative paths, where we
use the contextualised reference %kettle
in order to resolve a file path relative to the base directory of this
module, and secondly the Kettle testing assert function
kettle.test.assertJSONResponse
, which is a helpful all-in-one
utility for verifying an HTTP response status code as well as response payload. Note that
kettle.test.assertJSONResponse
actually runs two jqUnit assert functions, one on the response status code, and
another on the response being JSON (hence the expect: 2
).
To get a sense of the capabilities of a kettle.test.request.http
, you should browse node.js's documentation for its
http.request
, for which this component is a
wrapper. A kettle.test.request.http
component accepts a number of options configuring its function:
Supported configurable options for a kettle.test.request.http |
||
---|---|---|
Option | Type | Description |
path |
String (default: / ) |
The HTTP path to which this request is to be made |
method |
String (default: GET ) |
The HTTP method to be used to send the request |
port |
Number (default: 8081) |
The port number on the server for this request to connect to |
termMap |
Object (map of String to String ) |
The keys of this map are interpolated terms within the path option (considered as a
template where these keys will be prefixed by % ). The values will be interpolated directly
into the path. This structure will be merged with the option of the same name held in the
directOptions argument to the request component's send method. |
headers |
Object |
The HTTP headers to be sent with the request |
In addition, the kettle.test.request.http
component will accept any options accepted by node's native
http.request
constructor –
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 directOptions
argument to the component's send
method, described in the following section:
The primarily useful method on kettle.test.request.http
is send
. It accepts two arguments,
(model, directOptions)
:
Arguments accepted by send method of kettle.test.request.http |
||
---|---|---|
Option | Type | Description |
model |
Object/String (optional) |
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 Object it will be stringified as JSON, and if it is
a String it will be sent as the request body directly.
|
directOptions |
Object (optional) |
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 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 headers will be filled in by the implementation –
the header Content-Length will be populated automatically based on the supplied
model to the send method, and the header Content-Type will
default to application/json if no value is supplied.
|
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 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)
, which are described in the following table:
Arguments fired by the onComplete event of
kettle.test.request.http
|
||
---|---|---|
Option | Type | Description |
data |
String |
The request body received from the HTTP request |
that |
Component |
The kettle.test.request.http component itself. Note: By the time
this event fires, this component will have a member nativeResponse assigned, of type
http.IncomingMessage – this
object can be used to read off various standard pieces of the response to node.js's http.ClientRequest ,
including the HTTP statusCode , headers, etc.
|
parsedData |
Object |
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
the value of any standard header returned as set-cookie The latter is populated if a
cookieJar is configured in this component's tree which is capable of parsing cookies
encrypted with a "shared secret". Consult ths section on use of
cookies for more information.
|
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 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 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, which are documented in the following tables:
Options accepted by the kettle.test.assertJSONResponse helper function |
||
---|---|---|
Option | Type | Description |
string |
String |
The returned request body from the HTTP request |
request |
Component |
The kettle.test.request.http component which fired the request whose response is being
tested
|
statusCode |
Number (default: 200) |
The expected HTTP status code in ther response |
expected |
Object |
The expected response payload, encoded as an Object – comparison will be made using a
deep equality algorithm (jqUnit.assertDeepEq )
|
kettle.test.assertErrorResponse
will expect that the returned HTTP response body will parse as a JSON
structure. In addition to the checks described in this table, kettle.test.assertErrorResponse
will also
assert that the returned payload has an isError
member set to true
:
Options accepted by the kettle.test.assertErrorResponse helper function |
||
---|---|---|
Option | Type | Description |
string |
String |
The returned request body from the HTTP request |
request |
Component |
The kettle.test.request.http component which fired the request whose response is being
tested
|
statusCode |
Number (default: 500) |
The expected HTTP status code in ther response |
errorTexts |
String/Array of String |
A single String or array of String s which must appear at some index within
the message field of the returned JSON response payload
|
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 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 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, secret
, which is
broadcast both to the server and to the cookieJar (using Infusion's
distributeOptions directive) in order to enable them to
cooperate on transmitting signed cookies. Consult the framework tests at
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.
These tests are also a good example of configuring custom middleware into the middle of a
request's middleware chain. These tests include a middleware grade named kettle.tests.middleware.validateSession
which
will reject requests without a particular piece of populated session data, before processing reaches the request's
requestHandler
.
The kettle.test.request.form
grade derives from kettle.test.request.http
, but accepts additional configuration for
sending multipart-form-data
requests that can include a mix of fields and files.
In addition to all options from
kettle.test.request.http
, the grade accepts the following options:
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
ws.WebSocket for
which kettle.test.request.ws
is a wrapper. As with kettle.test.request.http
, messages are sent 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:
Supported configurable options for a kettle.test.request.ws |
||
---|---|---|
Option | Type | Description |
path |
String (default: / ) |
The HTTP path to which this request is to be made |
port |
Number (default: 8081) |
The port number on the server for this request to connect to |
sendJSON |
Boolean (default: true ) |
If this is set to true , the argument fired to the component's send method
will be encoded as JSON. Otherwise the argument will be sent to websocket.send as is.
|
receiveJSON |
Boolean (default: true ) |
If this is set to true , the argument received by listeners to the component's
onReceiveMessage event will be encoded as JSON. Otherwise the value will be transmitted as
from the WebSocket's message event unchanged.
|
webSocketsProtocols |
String/Array |
Forwarded to the protocols constructor argument of
ws.WebSocket
|
termMap |
Object (map of String to String ) |
The keys of this map are interpolated terms within the path option (considered as a
template where these keys will be prefixed by % ). The values will be interpolated directly
into the path. This structure will be merged with the option of the same name held in the
directOptions argument to the request component's send method.
|
headers |
Object |
The HTTP headers to be sent with the request |
In addition to the above options, any option may be supplied that is supported by the options
argument of
ws.WebSocket.
These include, in addition to the above, protocol
, agent
, protocolVersion
, hostname
.
The following events may be listened to on a kettle.test.request.ws
component:
Events attached to a kettle.test.request.ws |
||
---|---|---|
Event name | Arguments | Description |
onConnect |
(that: Component) |
Fired when the open event of the underlying ws.WebSocket is fired. This event
must be listened to in the fixture sequence before any attempt is made to fire messages from the
component with send |
onError |
(error: Object, that: Component, res:
http.IncomingMessage)
|
Fired either if an error occurs during the HTTP upgrade process, or if an error event is
fired from the ws.WebSocket object once the socket is established. For an error during
handshake, the error argument will be an object with isError: true and a
statusCode field taken from the HTTP statusCode. For an error event, the
error will be the original error payload.
|
onReceiveMessage |
(data: String/Object, that: Component) |
Fired whenever the underlying ws.WebSocket receives an message event.
If the receiveJSON option to the component is true this value will have been
JSON decoded.
|
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, the onConnect
event
documented in the above table will be fired on the component.
The signature of kettle.test.request.ws
send
is the same as that for kettle.test.request.http
, with a very similar
meaning:
The primarily useful method on kettle.test.request.ws
is send
. It accepts two arguments, (model, directOptions)
:
Arguments accepted by send method of kettle.test.request.ws |
||
---|---|---|
Option | Type | Description |
model |
Object/String |
The payload to be sent with the underlying ws.WebSocket.send call. If the component's
sendJSON option is set to true (the default), an Object sent here will be
automatically JSON-encoded. |
directOptions |
Object (optional) |
These options will be sent as the 2nd argument of
ws.WebSocket.send
|
Analogous with kettle.test.request.httpCookie
, there is a session-aware variant of the request grade
kettle.test.request.ws
, named kettle.test.request.wsCookie
. Its behaviour is identical with that of
kettle.test.request.httpCookie
, in particular being able to share access to the same kettle.test.cookieJar
component to enable a mixed series of HTTP and WebSockets requests
to be contextualised by the same session cookies.
Simply include all the test files in a single file using require
and run that file.
For example,
"use strict";
var fluid = require("infusion");
var kettle = require("kettle");
kettle.loadTestingSupport();
// array of paths to all tests (relative to the file containing this code) we want to run together
var testIncludes = [
"path/to/test/one",
"path/to/test/two"
// .
// .
// .
];
fluid.each(testIncludes, function (path) {
require(path);
});
For reference, you can see Kettle's own such file here
Please consult the test cases for the framework for more examples of Kettle primitives as well as the Kettle testing framework in action.