node-mocks-http
Version:
Mock 'http' objects for testing Express routing functions
524 lines (476 loc) • 15.8 kB
JavaScript
;
/**
* File: mockRequest
*
* This file implements node.js's implementation of a 'request' object.
* This is actually closer to what Express offers the user, in that the
* body is really a parsed object of values.
*
* @author Howard Abrams <howard.abrams@gmail.com>
*/
/**
* Function: createRequest
*
* Creates a new mock 'request' instance. All values are reset to the
* defaults.
*
* Parameters:
*
* options - An object of named parameters.
*
* Options:
*
* method - The method value, see <mockRequest._setMethod>
* url - The url value, see <mockRequest._setURL>
* originalUrl - The originalUrl value, see <mockRequest._setOriginalUrl>
* baseUrl - The baseUrl value, see <mockRequest._setBaseUrl>
* params - The parameters, see <mockRequest._setParam>
* body - The body values, , see <mockRequest._setBody>
*/
var url = require('url');
var typeis = require('type-is');
var accepts = require('accepts');
var parseRange = require('range-parser');
var EventEmitter = require('events').EventEmitter;
var utils = require('./utils');
var standardRequestOptions = [
'method', 'url', 'originalUrl', 'baseUrl', 'path', 'params', 'session', 'cookies', 'headers', 'body', 'query', 'files'
];
function createRequest(options) {
if (!options) {
options = {};
}
if (options.eventEmitter) {
EventEmitter = options.eventEmitter;
}
// create mockRequest
var mockRequest = Object.create(EventEmitter.prototype);
EventEmitter.call(mockRequest);
mockRequest.method = options.method ? options.method : 'GET';
mockRequest.url = options.url || options.path || '';
mockRequest.originalUrl = options.originalUrl || mockRequest.url;
mockRequest.baseUrl = options.baseUrl || mockRequest.url;
mockRequest.path = options.path ||
((options.url ? url.parse(options.url).pathname : ''));
mockRequest.params = options.params ? options.params : {};
if (options.session) {
mockRequest.session = options.session;
}
mockRequest.cookies = options.cookies ? options.cookies : {};
if (options.signedCookies) {
mockRequest.signedCookies = options.signedCookies;
}
mockRequest.headers = options.headers ? utils.convertKeysToLowerCase(options.headers) : {};
mockRequest.body = options.body ? options.body : {};
mockRequest.query = options.query ? options.query : {};
mockRequest.files = options.files ? options.files : {};
mockRequest.socket = options.socket ? options.socket : {};
//parse query string from url to object
if (Object.keys(mockRequest.query).length === 0) {
mockRequest.query = require('querystring').parse(mockRequest.url.split('?')[1]);
if (!mockRequest.query.hasOwnProperty) {
Object.defineProperty(
mockRequest.query,
'hasOwnProperty',
{
enumerable: false,
value: Object.hasOwnProperty.bind(mockRequest.query)
}
);
}
}
// attach any other provided objects into the request for more advanced testing
for (var n in options) {
if (standardRequestOptions.indexOf(n) === -1) {
mockRequest[n] = options[n];
}
}
/**
* Return request header.
*
* The `Referrer` header field is special-cased,
* both `Referrer` and `Referer` are interchangeable.
*
* Examples:
*
* mockRequest.get('Content-Type');
* // => "text/plain"
*
* mockRequest.get('content-type');
* // => "text/plain"
*
* mockRequest.get('Something');
* // => undefined
*
* Aliased as `mockRequest.header()`.
*
* @param {String} name
* @return {String}
* @api public
*/
mockRequest.get =
mockRequest.header = function(name) {
name = name.toLowerCase();
switch (name) {
case 'referer':
case 'referrer':
return mockRequest.headers.referrer || mockRequest.headers.referer;
default:
return mockRequest.headers[name];
}
};
/**
* Function: is
*
* Checks for matching content types in the content-type header.
* Requires a request body, identified by transfer-encoding or content-length headers
*
* Examples:
*
* mockRequest.headers['content-type'] = 'text/html';
* mockRequest.headers['transfer-encoding'] = 'chunked';
* mockRequest.headers['content-length'] = '100';
*
* mockRequest.is('html');
* // => "html"
*
* mockRequest.is('json');
* // => false
*
* mockRequest.is(['json', 'html', 'text']);
* // => "html"
*
* @param {String|String[]} types content type or array of types to match
* @return {String|false|null} Matching content type as string, false if no match, null if request has no body.
* @api public
*/
mockRequest.is = function(types) {
if (!Array.isArray(types)) {
types = [].slice.call(arguments);
}
return typeis(mockRequest, types);
};
/**
* Function: accepts
*
* Checks for matching content types in the Accept header.
*
* Examples:
*
* mockRequest.headers['accept'] = 'application/json'
*
* mockRequest.accepts('json');
* // => 'json'
*
* mockRequest.accepts('html');
* // => false
*
* mockRequest.accepts(['html', 'json']);
* // => 'json'
*
* @param {String|String[]} types Mime type(s) to check against
* @return {String|false} Matching type or false if no match.
*/
mockRequest.accepts = function(types) {
var Accepts = accepts(mockRequest);
return Accepts.type(types);
};
/**
* Check if the given `encoding`s are accepted.
*
* @param {String} ...encoding
* @return {String|Array}
* @public
*/
mockRequest.acceptsEncodings = function(encodings){
if (!Array.isArray(encodings)) {
encodings = [].slice.call(arguments);
}
var accept = accepts(mockRequest);
return accept.encodings(encodings);
};
/**
* Check if the given `charset`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
* @param {String} ...charset
* @return {String|Array}
* @public
*/
mockRequest.acceptsCharsets = function(charsets){
if (!Array.isArray(charsets)) {
charsets = [].slice.call(arguments);
}
var accept = accepts(mockRequest);
return accept.charsets(charsets);
};
/**
* Check if the given `lang`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
* @param {String} ...lang
* @return {String|Array}
* @public
*/
mockRequest.acceptsLanguages = function(languages){
if (!Array.isArray(languages)) {
languages = [].slice.call(arguments);
}
var accept = accepts(mockRequest);
return accept.languages(languages);
};
/**
* Function: range
*
* Parse Range header field, capping to the given `size`.
*
* Unspecified ranges such as "0-" require knowledge of your resource length. In
* the case of a byte range this is of course the total number of bytes. If the
* Range header field is not given `undefined` is returned, `-1` when unsatisfiable,
* and `-2` when syntactically invalid.
*
* When ranges are returned, the array has a "type" property which is the type of
* range that is required (most commonly, "bytes"). Each array element is an object
* with a "start" and "end" property for the portion of the range.
*
* The "combine" option can be set to `true` and overlapping & adjacent ranges
* will be combined into a single range.
*
* NOTE: remember that ranges are inclusive, so for example "Range: users=0-3"
* should respond with 4 users when available, not 3.
*
* @param {number} size
* @param {object} [opts]
* @param {boolean} [opts.combine=false]
* @return {false|number|array}
* @public
*/
mockRequest.range = function(size, opts) {
var range = mockRequest.get('Range');
if (!range) {
return;
}
return parseRange(size, range, opts);
};
/**
* Function: param
*
* Return the value of param name when present.
* Lookup is performed in the following order:
* - req.params
* - req.body
* - req.query
*/
mockRequest.param = function(parameterName, defaultValue) {
if (mockRequest.params.hasOwnProperty(parameterName)) {
return mockRequest.params[parameterName];
} else if (mockRequest.body.hasOwnProperty(parameterName)) {
return mockRequest.body[parameterName];
} else if (mockRequest.query.hasOwnProperty(parameterName)) {
return mockRequest.query[parameterName];
}
return defaultValue;
};
/**
* Function: _setParameter
*
* Set parameters that the client can then get using the 'params'
* key.
*
* Parameters:
*
* key - The key. For instance, 'bob' would be accessed: request.params.bob
* value - The value to return when accessed.
*/
mockRequest._setParameter = function(key, value) {
mockRequest.params[key] = value;
};
/**
* Sets a variable that is stored in the session.
*
* @param variable The variable to store in the session
* @param value The value associated with the variable
*/
mockRequest._setSessionVariable = function(variable, value) {
if (typeof mockRequest.session !== 'object') {
mockRequest.session = {};
}
mockRequest.session[variable] = value;
};
/**
* Sets a variable that is stored in the cookies.
*
* @param variable The variable to store in the cookies
* @param value The value associated with the variable
*/
mockRequest._setCookiesVariable = function(variable, value) {
mockRequest.cookies[variable] = value;
};
/**
* Sets a variable that is stored in the signed cookies.
*
* @param variable The variable to store in the signed cookies
* @param value The value associated with the variable
*/
mockRequest._setSignedCookiesVariable = function(variable, value) {
if (typeof mockRequest.signedCookies !== 'object') {
mockRequest.signedCookies = {};
}
mockRequest.signedCookies[variable] = value;
};
/**
* Sets a variable that is stored in the headers.
*
* @param variable The variable to store in the headers
* @param value The value associated with the variable
*/
mockRequest._setHeadersVariable = function(variable, value) {
variable = variable.toLowerCase();
mockRequest.headers[variable] = value;
};
/**
* Sets a variable that is stored in the files.
*
* @param variable The variable to store in the files
* @param value The value associated with the variable
*/
mockRequest._setFilesVariable = function(variable, value) {
mockRequest.files[variable] = value;
};
/**
* Function: _setMethod
*
* Sets the HTTP method that the client gets when the called the 'method'
* property. This defaults to 'GET' if it is not set.
*
* Parameters:
*
* method - The HTTP method, e.g. GET, POST, PUT, DELETE, etc.
*
* Note: We don't validate the string. We just return it.
*/
mockRequest._setMethod = function(method) {
mockRequest.method = method;
};
/**
* Function: _setURL
*
* Sets the URL value that the client gets when the called the 'url'
* property.
*
* Parameters:
*
* value - The request path, e.g. /my-route/452
*
* Note: We don't validate the string. We just return it. Typically, these
* do not include hostname, port or that part of the URL.
*/
mockRequest._setURL = function(value) {
mockRequest.url = value;
};
/**
* Function: _setBaseUrl
*
* Sets the URL value that the client gets when the called the 'baseUrl'
* property.
*
* Parameters:
*
* value - The request base path, e.g. /my-route
*
* Note: We don't validate the string. We just return it. Typically, these
* do not include hostname, port or that part of the URL.
*/
mockRequest._setBaseUrl = function(value) {
mockRequest.baseUrl = value;
};
/**
* Function: _setOriginalUrl
*
* Sets the URL value that the client gets when the called the 'originalUrl'
* property.
*
* Parameters:
*
* value - The request path, e.g. /my-route/452
*
* Note: We don't validate the string. We just return it. Typically, these
* do not include hostname, port or that part of the URL.
*/
mockRequest._setOriginalUrl = function(value) {
mockRequest.originalUrl = value;
};
/**
* Function: _setBody
*
* Sets the body that the client gets when the called the 'body'
* parameter. This defaults to 'GET' if it is not set.
*
* Parameters:
*
* body - An object representing the body.
*
* If you expect the 'body' to come from a form, this typically means that
* it would be a flat object of properties and values, as in:
*
* > { name: 'Howard Abrams',
* > age: 522
* > }
*
* If the client is expecting a JSON object through a REST interface, then
* this object could be anything.
*/
mockRequest._setBody = function(body) {
mockRequest.body = body;
};
/**
* Function: _addBody
*
* Adds another body parameter the client gets when calling the 'body'
* parameter with another property value, e.g. the name of a form element
* that was passed in.
*
* Parameters:
*
* key - The key. For instance, 'bob' would be accessed: request.params.bob
* value - The value to return when accessed.
*/
mockRequest._addBody = function(key, value) {
mockRequest.body[key] = value;
};
/**
* Function: send
*
* Write data to the request stream which will trigger request's 'data', and 'end' event
*
* Parameters:
*
* data - string, array, object, number, buffer
*/
mockRequest.send = function(data) {
if(Buffer.isBuffer(data)){
this.emit('data', data);
}else if(typeof data === 'object' || typeof data === 'number'){
this.emit('data', Buffer.from(JSON.stringify(data)));
}else if(typeof data === 'string'){
this.emit('data', Buffer.from(data));
}
this.emit('end');
};
/**
* Function: subdomains
*
* Subdomains are the dot-separated parts of the host before the main domain of the app.
*
*/
mockRequest.subdomains = (function() {
if (!mockRequest.headers.host) {
return [];
}
var offset = 2;
var subdomains = mockRequest.headers.host.split('.').reverse();
return subdomains.slice(offset);
}());
return mockRequest;
}
module.exports.createRequest = createRequest;