Skip to content

Latest commit

 

History

History
569 lines (503 loc) · 25.5 KB

KettleTestingFramework.md

File metadata and controls

569 lines (503 loc) · 25.5 KB
title layout category
The Kettle Testing Framework
default
Kettle

The Kettle Testing Framework

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.

A simple Kettle testing framework example

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).

Configuration options available on kettle.test.request.http

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:

Using a kettle.test.request.http – the send method

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.

Listening for a response from kettle.test.request.http – the onComplete event

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.

Helper methods for making assertions on onComplete

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 Strings which must appear at some index within the message field of the returned JSON response payload

Using cookies with an HTTP testing request

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.

Sending multipart-form testing fixtures via POST with kettle.test.request.form

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.

Configuration options available on kettle.test.request.form

In addition to all options from kettle.test.request.http, the grade accepts the following options:

Issuing WebSockets testing fixtures with kettle.test.request.ws

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.

Events attached to a kettle.test.request.ws

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.

Connecting a kettle.test.request.ws client with the connect method

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.

Sending a message using the send method of kettle.test.request.ws

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

Issuing session-aware WebSockets requests

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.

Running multiple tests together

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

Framework tests

Please consult the test cases for the framework for more examples of Kettle primitives as well as the Kettle testing framework in action.