helma/modules/jala/util/Test/code/Global/jala.Test.js

1598 lines
51 KiB
JavaScript

//
// Jala Project [http://opensvn.csie.org/traccgi/jala]
//
// Copyright 2004 ORF Online und Teletext GmbH
//
// Licensed under the Apache License, Version 2.0 (the ``License'');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an ``AS IS'' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// $Revision$
// $LastChangedBy$
// $LastChangedDate$
// $HeadURL$
//
/**
* @fileoverview Fields and methods of the jala.Test class.
*/
// Define the global namespace for Jala modules
if (!global.jala) {
global.jala = {};
}
/**
* HelmaLib dependencies
*/
app.addRepository("modules/core/String.js");
app.addRepository("modules/helma/Http.js");
/**
* Jala dependencies
*/
app.addRepository(getProperty("jala.dir", "modules/jala") +
"/code/Database.js");
/**
* Constructs a new Test instance.
* @class Provides various methods for automated tests.
* This is essentially a port of JSUnit (http://www.edwardh.com/jsunit/)
* suitable for testing Helma applications.
* @param {Number} capacity The capacity of the cache
* @constructor
*/
jala.Test = function() {
/**
* Contains the number of tests that were executed
* @type Number
*/
this.testsRun = 0;
/**
* Contains the number of tests that failed
* @type Boolean
*/
this.testsFailed = 0;
/**
* Contains the number of test functions that passed
* @type Number
*/
this.functionsPassed = 0;
/**
* Contains the number of test functions that failed
* @type Number
*/
this.functionsFailed = 0;
/**
* An Array containing the results of this Test instance.
* @type Array
*/
this.results = [];
return this;
};
/*************************************************************
***** S T A T I C F I E L D S A N D M E T H O D S *****
*************************************************************/
/**
* Constant indicating "passed" status
* @type String
* @final
*/
jala.Test.PASSED = "passed";
/**
* Constant indicating "failed" status
* @type String
* @final
*/
jala.Test.FAILED = "failed";
/**
* Helper method useable for displaying a value
* @param {Object} The value to render
* @returns The value rendered as string
* @type String
*/
jala.Test.valueToString = function(val) {
res.push();
if (val === null) {
res.write("null");
} else if (val === undefined) {
res.write("undefined");
} else {
if (typeof(val) == "function") {
// functions can be either JS methods or Java classes
// the latter throws an exception when trying to access a property
try {
res.write(val.name || val);
} catch (e) {
res.write(val);
}
} else {
if (val.constructor && val.constructor == String) {
res.write('"' + encode(val.head(200)) + '"');
} else {
res.write(val.toString());
}
res.write(" (");
if (val.constructor && val.constructor.name != null) {
res.write(val.constructor.name);
} else {
res.write(typeof(val));
}
res.write(")");
}
}
return res.pop();
};
/**
* Returns the directory containing the test files.
* The location of the directory is either defined by the
* application property "jala.testDir" or expected to be one level
* above the application directory (and named "tests")
* @returns The directory containing the test files
* @type helma.File
*/
jala.Test.getTestsDirectory = function() {
var dir;
if (getProperty("jala.testDir") != null) {
dir = new helma.File(getProperty("jala.testDir"));
}
if (!dir || !dir.exists()) {
var appDir = new helma.File(app.dir);
dir = new helma.File(appDir.getParent(), "tests");
if (!dir.exists())
return null;
}
return dir;
};
/**
* Returns an array containing the test files located
* in the directory.
* @returns An array containing the names of all test files
* @type Array
*/
jala.Test.getTestFiles = function() {
var dir;
if ((dir = jala.Test.getTestsDirectory()) != null) {
return dir.list(/.*\.js$/).sort();
}
return null;
};
/**
* Returns the testfile with the given name
* @param {String} fileName The name of the test file
* @returns The test file
* @type helma.File
*/
jala.Test.getTestFile = function(fileName) {
var dir = jala.Test.getTestsDirectory();
if (dir != null) {
return new helma.File(dir, fileName);
}
return null;
};
/**
* @param {Number} nr The number of arguments to be expected
* @param {Object} args The arguments array.
* @returns True in case the number of arguments matches
* the expected amount, false otherwise.
* @type Boolean
*/
jala.Test.evalArguments = function(args, argsExpected) {
if (!(args.length == argsExpected ||
(args.length == argsExpected + 1 && typeof(args[0]) == "string"))) {
throw new jala.Test.ArgumentsException("Insufficient arguments passed to assertion function");
}
return;
};
/**
* Returns true if the arguments array passed as argument
* contains an additional comment.
* @param {Array} args The arguments array to check for an existing comment.
* @param {Number} argsExpected The number of arguments expected by the
* assertion function.
* @returns True if the arguments contain a comment, false otherwise.
* @type Boolean
*/
jala.Test.argsContainComment = function(args, argsExpected) {
return !(args.length <= argsExpected
|| (args.length == argsExpected + 1 && typeof(args[0]) != "string"))
};
/**
* Cuts out the comment from the arguments array passed
* as argument and returns it. CAUTION: this actually modifies
* the arguments array!
* @param {Array} args The arguments array.
* @returns The comment, if existing. Null otherwise.
* @type String
*/
jala.Test.getComment = function(args, argsExpected) {
if (jala.Test.argsContainComment(args, argsExpected))
return args[0];
return null;
};
/**
* Returns the argument on the index position in the array
* passed as arguments. This method respects an optional comment
* at the beginning of the arguments array.
* @param {Array} args The arguments to retrieve the non-comment
* value from.
* @param {Number} idx The index position on which the value to
* retrieve is to be expected if <em>no</em> comment is existing.
* @returns The non-comment value, or null.
* @type Object
*/
jala.Test.getValue = function(args, argsExpected, idx) {
return jala.Test.argsContainComment(args, argsExpected) ? args[idx+1] : args[idx];
};
/**
* Creates a stack trace and parses it for display.
* @param {java.lang.StackTraceElement} trace The trace to parse. If not given
* a stacktrace will be generated
* @returns The parsed stack trace
* @type String
*/
jala.Test.getStackTrace = function(trace) {
/**
* Private method for filtering out only JS parts of the stack trace
* @param {Object} name
*/
var accept = function(name) {
return name.endsWith(".js") || name.endsWith(".hac") ||
name.endsWith(".hsp");
};
// create exception and fill in stack trace
if (!trace) {
var ex = new Packages.org.mozilla.javascript.EvaluatorException("");
ex.fillInStackTrace();
trace = ex.getStackTrace();
}
var stack = [];
var el, fileName, lineNumber;
// parse the stack trace and keep only the js elements
var inTrace = false;
for (var i=trace.length; i>0; i--) {
el = trace[i-1];
fileName = el.getFileName();
lineNumber = el.getLineNumber();
if (fileName != null && lineNumber > -1 && accept(fileName)) {
if (fileName.endsWith(res.meta.currentTest)) {
inTrace = true;
}
if (inTrace == true) {
// ignore all trace lines that refer to jala.Test
if (fileName.endsWith("jala.Test.js")) {
break;
}
stack.push("at " + fileName + ":" + lineNumber);
}
}
}
return stack.reverse().join("\n");
};
/**
* Adds all assertion methods, the http client, test database manager and
* smpt server to the per-thread global object.
* @private
*/
jala.Test.prepareTestScope = function() {
// define global assertion functions
for (var i in jala.Test.prototype) {
if (i.indexOf("assert") == 0) {
global[i] = jala.Test.prototype[i];
}
}
// add global include method
global.include = function(file) {
jala.Test.include(global, file);
return;
};
// instantiate a global HttpClient
global.httpClient = new jala.Test.HttpClient();
// instantiate the test database manager
global.testDatabases = new jala.Test.DatabaseMgr();
// instantiate the smtp server
global.smtpServer = new jala.Test.SmtpServer();
return;
};
/**
* Evaluates a javascript file in the global test scope. This method can be used
* to include generic testing code in test files. This method is available as
* global method "include" for all test methods
* @param {Object} scope The scope in which the file should be evaluated
* @param {String} fileName The name of the file to include, including the path
*/
jala.Test.include = function(scope, file) {
var file = new helma.File(file);
var fileName = file.getName();
if (file.canRead() && file.exists()) {
var cx = Packages.org.mozilla.javascript.Context.enter();
var code = new java.lang.String(file.readAll() || "");
cx.evaluateString(scope, code, fileName, 1, null);
}
return;
};
/*******************************
***** E X C E P T I O N S *****
*******************************/
/**
* Creates a new Exception instance
* @class Base exception class
* @returns A newly created Exception instance
* @constructor
*/
jala.Test.Exception = function Exception() {
return this;
};
/** @ignore */
jala.Test.Exception.prototype.toString = function() {
return "[jala.Test.Exception: " + this.message + "]";
};
/**
* Creates a new TestException instance
* @class Instances of this exception are thrown whenever an
* assertion function fails
* @param {String} comment An optional comment
* @param {String} message The failure message
* @returns A newly created TestException instance
* @constructor
*/
jala.Test.TestException = function TestException(comment, message) {
this.functionName = null;
this.comment = comment;
this.message = message;
this.stackTrace = jala.Test.getStackTrace();
return this;
};
jala.Test.TestException.prototype = new jala.Test.Exception();
/** @ignore */
jala.Test.TestException.prototype.toString = function() {
return "[jala.Test.TestException in " + this.functionName +
": " + this.message + "]";
};
/**
* Creates a new ArgumentsException instance
* @class Instances of this exception are thrown whenever an assertion
* function is called with incorrect or insufficient arguments
* @param {String} message The failure message
* @returns A newly created ArgumentsException instance
* @constructor
*/
jala.Test.ArgumentsException = function ArgumentsException(message) {
this.functionName = null;
this.message = message;
this.stackTrace = jala.Test.getStackTrace();
return this;
};
jala.Test.ArgumentsException.prototype = new jala.Test.Exception();
/** @ignore */
jala.Test.ArgumentsException.prototype.toString = function() {
return "[jala.Test.ArgumentsException in " + this.functionName +
": " + this.message + "]";
};
/**
* Creates a new EvaluatorException instance
* @class Instances of this exception are thrown when attempt
* to evaluate the test code fails.
* @param {String} message The failure message, or an Error previously
* thrown.
* @param {String} exception An optional nested Error
* @returns A newly created EvaluatorException instance
* @constructor
*/
jala.Test.EvaluatorException = function EvaluatorException(message, exception) {
this.functionName = null;
this.message = null;
this.stackTrace = null;
this.fileName = null;
this.lineNumber = null;
if (arguments.length == 1 && arguments[0] instanceof Error) {
this.message = "";
exception = arguments[0];
} else {
this.message = message;
}
if (exception != null) {
this.name = exception.name;
if (exception.rhinoException != null) {
var e = exception.rhinoException;
this.message += e.details();
this.stackTrace = jala.Test.getStackTrace(e.getStackTrace());
} else if (exception instanceof Error) {
this.message = exception.message;
}
if (!this.stackTrace) {
// got no stack trace, so add at least filename and line number
this.fileName = exception.fileName || null;
this.lineNumber = exception.lineNumber || null;
}
}
return this;
};
jala.Test.EvaluatorException.prototype = new jala.Test.Exception();
/** @ignore */
jala.Test.EvaluatorException.prototype.toString = function() {
return "[jala.Test.EvaluatorException: " + this.message + "]";
};
/*************************************************
***** R E S U L T C O N S T R U C T O R S *****
*************************************************/
/**
* Constructs a new TestResult instance
* @class Instances of this class represent the result of the execution
* of a single test file
* @param {String} testFileName The name of the excecuted test file
* @returns A new TestResult instance
* @constructor
*/
jala.Test.TestResult = function(testFileName) {
this.name = testFileName;
this.elapsed = 0;
this.status = jala.Test.PASSED;
this.log = [];
return this;
};
/**
* Constructs a new TestFunctionResult instance
* @class Instances of this class represent the result of the successful
* execution of a single test function (failed executions will be represented
* as Exceptions in the log of a test result).
* @param {String} functionName The name of the excecuted test function
* @param {Date} startTime A Date instance marking the begin of the test
* @returns A new TestFunctionResult instance
* @constructor
*/
jala.Test.TestFunctionResult = function(functionName, startTime) {
this.functionName = functionName;
this.elapsed = (new Date()) - startTime;
return this;
};
/*************************************************
***** P R O T O T Y P E F U N C T I O N S *****
*************************************************/
/**
* Executes a single test file
* @param {helma.File} testFile The file containing the test to run
*/
jala.Test.prototype.executeTest = function(testFile) {
var testFileName = testFile.getName();
// store the name of the current test in res.meta.currentTest
// as this is needed in getStackTrace
res.meta.currentTest = testFileName;
var cx = Packages.org.mozilla.javascript.Context.enter();
var code = new java.lang.String(testFile.readAll() || "");
var testResult = new jala.Test.TestResult(testFileName);
global.testFunctionIdents = [];
try {
// prepare the test scope
jala.Test.prepareTestScope();
// evaluate the test file in the per-thread which is garbage
// collected at the end of the test run
cx.evaluateString(global, code, testFileName, 1, null);
for (var ident in global) {
if (ident.startsWith("test") && (global[ident] instanceof Function)) {
testFunctionIdents.push(ident);
}
}
var start = new Date();
// run all test methods defined in the array "tests"
var functionName;
for (var i=0;i<global.testFunctionIdents.length;i++) {
// execute the setup function, if defined
if (global.setup != null && global.setup instanceof Function) {
global.setup();
}
try {
functionName = global.testFunctionIdents[i];
if (!global[functionName] || global[functionName].constructor != Function) {
throw new jala.Test.EvaluatorException("Test function '" +
functionName + "' is not defined.");
}
testResult.log.push(this.executeTestFunction(functionName, global));
} catch (e) {
this.testsFailed += 1;
testResult.status = jala.Test.FAILED;
e.functionName = functionName;
testResult.log.push(e);
} finally {
// execute the cleanup function, if defined
if (global.cleanup != null && global.cleanup instanceof Function) {
global.cleanup();
}
}
}
} catch (e) {
this.testsFailed += 1;
testResult.status = jala.Test.FAILED;
testResult.log.push(new jala.Test.EvaluatorException(e));
// execute the cleanup function, if defined
if (global.cleanup != null && global.cleanup instanceof Function) {
global.cleanup();
}
} finally {
// exit the js context created above
cx.exit();
// FIXME (sim) don't polute global in the first place or
// get a fresh global for each testrun
global.testFunctionIdents.forEach(function(funcName) {
// NOTE won't work on var-defined props
// delete global[funcName]]
global[funcName] = "ignoreMe";
}, this);
global["setup"] = "ignoreMe";
global["cleanup"] = "ignoreMe";
// clear res.meta.currentTest
res.meta.currentTest = null;
}
testResult.elapsed = (new Date()) - start;
this.results.push(testResult);
return;
};
/**
* Executes a single test function
* @param {String} functionName The name of the test function to execute
* @param {helma.scripting.rhino.GlobalObject} global The scope to execute
* the test method in
*/
jala.Test.prototype.executeTestFunction = function(functionName, scope) {
// store the name of the current function in res.meta.currentTestFunction
res.meta.currentTestFunction = functionName;
var start = new Date();
try {
scope[functionName]();
this.functionsPassed += 1;
return new jala.Test.TestFunctionResult(functionName, start);
} catch (e) {
if (!(e instanceof jala.Test.Exception)) {
e = new jala.Test.EvaluatorException(e);
}
this.functionsFailed += 1;
throw e;
} finally {
// clear res.meta.currentFunction
res.meta.currentTestFunction = null;
}
};
/**
* Main test execution function
* @param {String|Array} what Either the name of a single test file
* or an array containing the names of several function files that should
* be executed.
*/
jala.Test.prototype.execute = function(what) {
var self = this;
var executeTest = function(fileName) {
var file = jala.Test.getTestFile(fileName);
if (file != null && file.exists()) {
self.testsRun += 1;
self.executeTest(file);
}
};
if (what instanceof Array) {
for (var i in what) {
executeTest(what[i]);
}
} else {
executeTest(what);
}
return;
};
/** @ignore */
jala.Test.prototype.toString = function() {
return "[jala.Test]";
};
/**
* Renders the results of all tests done by this test instance
* to response.
*/
jala.Test.prototype.renderResults = function() {
if (this.results.length > 0) {
for (var i=0;i<this.results.length;i++) {
this.renderResult(this.results[i]);
}
}
return;
};
/**
* Renders the result of a single test
* @param {jala.Test.TestResult} The result to render
*/
jala.Test.prototype.renderResult = function(result) {
res.push();
var logItem;
for (var i=0;i<result.log.length;i++) {
logItem = result.log[i];
if (logItem instanceof jala.Test.Exception) {
renderSkin("jala.Test#logFailed", logItem);
} else {
renderSkin("jala.Test#logPassed", logItem);
}
}
var param = {
name: result.name,
elapsed: result.elapsed,
status: result.status,
log: res.pop()
}
renderSkin("jala.Test#result", param);
return;
};
/***********************
***** M A C R O S *****
***********************/
/**
* Renders the list of available tests
*/
jala.Test.prototype.list_macro = function() {
var list = jala.Test.getTestFiles();
if (list && list.length > 0) {
var fileName, skinParam;
for (var i=0;i<list.length;i++) {
fileName = list[i];
skinParam = {name: fileName};
if (req.data.test == fileName ||
(req.data.test_array && req.data.test_array.contains(fileName))) {
skinParam.checked = "checked";
}
renderSkin("jala.Test#item", skinParam);
}
}
return;
};
/**
* Renders the test results
*/
jala.Test.prototype.results_macro = function() {
this.renderResults();
return;
};
/**
* Returns the absolute path to the directory containing the tests
* @returns The path to the tests directory
* @type String
*/
jala.Test.prototype.directory_macro = function() {
return jala.Test.getTestsDirectory();
};
/***********************************************************************
***** A S S E R T I O N A N D H E L P E R F U N C T I O N S *****
***********************************************************************/
/**
* Checks if the value passed as argument is boolean true.
* @param {Object} val The value that should be boolean true.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertTrue = function assertTrue(val) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
if (typeof(value) != "boolean") {
throw new jala.Test.ArgumentsException("Invalid argument to assertTrue(boolean): " +
jala.Test.valueToString(value));
} else if (value !== true) {
throw new jala.Test.TestException(comment,
"assertTrue(boolean) called with argument " +
jala.Test.valueToString(value));
}
return;
};
/**
* Checks if the value passed as argument is boolean false.
* @param {Object} val The value that should be boolean false.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertFalse = function assertFalse(val) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
if (typeof(value) != "boolean") {
throw new jala.Test.ArgumentsException("Invalid argument to assertFalse(boolean): " +
jala.Test.valueToString(value));
} else if (value === true) {
throw new jala.Test.TestException(comment,
"assertFalse(boolean) called with argument " +
jala.Test.valueToString(value));
}
return;
};
/**
* Checks if the values passed as arguments are equal.
* @param {Object} val1 The value that should be compared to the second argument.
* @param {Object} val2 The value that should be compared to the first argument.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertEqual = function assertEqual(val1, val2) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value1 = jala.Test.getValue(arguments, argsExpected, 0);
var value2 = jala.Test.getValue(arguments, argsExpected, 1);
if (value1 !== value2) {
throw new jala.Test.TestException(comment,
"Expected " + jala.Test.valueToString(value1) +
" to be equal to " + jala.Test.valueToString(value2));
}
return;
};
/**
* Checks if the values passed as arguments are arrays and contain the same elements
* @param val1 the first array
* @param val2 the second array
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertEqualArrays = function assertEqualArrays(val1, val2) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value1 = jala.Test.getValue(arguments, argsExpected, 0);
var value2 = jala.Test.getValue(arguments, argsExpected, 1);
if (!(value1 instanceof Array) || !(value2 instanceof Array)) {
throw new jala.Test.ArgumentsException("Invalid arguments to assertEqualArrays: " +
jala.Test.valueToString(value1) + ", " + jala.Test.valueToString(value2));
}
var equal = false;
if (value1.length == value2.length) {
equal = value1.every(function(element, index, array) {
return (element === value2[index]);
});
}
if (!equal) {
throw new jala.Test.TestException(comment,
"Expected " + jala.Test.valueToString(value1) +
" to be equal to " + jala.Test.valueToString(value2));
}
return;
};
/**
* Checks if the value passed as argument equals the content of a file on disk.
* @param {Object} val The value that should be compared with the content of
* the file on disk.
* @param {String|helma.File} file Either a file name (including a path), or
* an instance of helma.File representing the file to use for comparison.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertEqualFile = function assertEqualFile(val, file) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value1 = jala.Test.getValue(arguments, argsExpected, 0);
var file = new helma.File(jala.Test.getValue(arguments, argsExpected, 1));
var equals;
if (value1.getClass && value1.getClass().isArray() &&
value1.getClass().getComponentType() === java.lang.Byte.TYPE) {
equals = java.util.Arrays.equals(value1, file.toByteArray());
} else {
// remove the last linefeed in value1, since readAll() removes
// the last linefeed in a file too
var str = value1.replace(/\r?\n$/g, "");
equals = str === file.readAll();
}
if (!equals) {
throw new jala.Test.TestException(comment,
"Expected " + jala.Test.valueToString(value1) +
" to be equal to the contents of the file " +
file.getAbsolutePath());
}
return;
};
/**
* Checks if the values passed as arguments are not equal.
* @param {Object} val1 The value that should be compared to the second argument.
* @param {Object} val2 The value that should be compared to the first argument.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertNotEqual = function assertNotEqual(val1, val2) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var value1 = jala.Test.getValue(arguments, argsExpected, 0);
var value2 = jala.Test.getValue(arguments, argsExpected, 1);
var comment = jala.Test.getComment(arguments, argsExpected);
if (value1 === value2) {
throw new jala.Test.TestException(comment,
"Expected " + jala.Test.valueToString(value1) +
" to be not equal to " + jala.Test.valueToString(value2));
}
return;
};
/**
* Checks if the value passed as argument is null.
* @param {Object} val The value that should be null.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertNull = function assertNull(val) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
if (value !== null) {
throw new jala.Test.TestException(comment,
"Expected " + jala.Test.valueToString(value) +
" to be null");
}
return;
};
/**
* Checks if the value passed as argument is not null.
* @param {Object} val The value that should be not null.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertNotNull = function assertNotNull(val) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
if (value === null) {
throw new jala.Test.TestException(comment,
"Expected " + jala.Test.valueToString(value) +
" to be not null");
}
return;
};
/**
* Checks if the value passed as argument is undefined.
* @param {Object} val The value that should be undefined.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertUndefined = function assertUndefined(val) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
if (value !== undefined) {
throw new jala.Test.TestException(comment,
"Expected " + jala.Test.valueToString(value) +
" to be undefined");
}
return;
};
/**
* Checks if the value passed as argument is not undefined.
* @param {Object} val The value that should be not undefined.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertNotUndefined = function assertNotUndefined(val) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
if (value === undefined) {
throw new jala.Test.TestException(comment,
"Expected argument to be not undefined");
}
return;
};
/**
* Checks if the value passed as argument is NaN.
* @param {Object} val The value that should be NaN.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertNaN = function assertNaN(val) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
if (!isNaN(value)) {
throw new jala.Test.TestException(comment,
"Expected " + jala.Test.valueToString(value) +
" to be NaN");
}
return;
};
/**
* Checks if the value passed as argument is not NaN.
* @param {Object} val The value that should be not NaN.
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertNotNaN = function assertNotNaN(val) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
if (isNaN(value)) {
throw new jala.Test.TestException(comment,
"Expected " + jala.Test.valueToString(value) +
" to be a number");
}
return;
};
/**
* Checks if the value passed as argument contains the pattern specified.
* @param {String} val The string that should contain the pattern
* @param {String} str The string that should be contained
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertStringContains = function assertStringContains(val, str) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
var pattern = jala.Test.getValue(arguments, argsExpected, 1);
if (pattern.constructor == String) {
if (value.indexOf(pattern) < 0) {
throw new jala.Test.TestException(comment,
"Expected string " + jala.Test.valueToString(pattern) +
" to be found in " + jala.Test.valueToString(value));
}
} else {
throw new jala.Test.ArgumentsException("Invalid argument to assertStringContains(string, string): " +
jala.Test.valueToString(pattern));
}
return;
};
/**
* Checks if the regular expression matches the string.
* @param {String} val The string that should contain the regular expression pattern
* @param {RegExp} rxp The regular expression that should match the value
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertMatch = function assertMatch(val, rxp) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
jala.Test.evalArguments(arguments, argsExpected);
var comment = jala.Test.getComment(arguments, argsExpected);
var value = jala.Test.getValue(arguments, argsExpected, 0);
var exp = jala.Test.getValue(arguments, argsExpected, 1);
if (exp.constructor == RegExp) {
if (exp.test(value) == false) {
throw new jala.Test.TestException(comment,
"Expected pattern " + jala.Test.valueToString(exp) +
" to match " + jala.Test.valueToString(value));
}
} else {
throw new jala.Test.ArgumentsException("Invalid argument to assertMatch(string, regexp): " +
jala.Test.valueToString(pattern));
}
return;
};
/**
* Checks if the function passed as argument throws a defined exception.
* @param {Object} func The function to call
* @param {Object} exception Optional object expected to be thrown when executing
* the function
* @throws jala.Test.ArgumentsException
* @throws jala.Test.TestException
*/
jala.Test.prototype.assertThrows = function assertThrows(func, exception) {
var functionName = arguments.callee.name;
var argsExpected = arguments.callee.length;
if (!func || !(func instanceof Function)) {
throw new jala.Test.ArgumentsException("Insufficient arguments passed to assertion function");
}
var comment = jala.Test.getComment(arguments, argsExpected);
var func = jala.Test.getValue(arguments, argsExpected, 0);
var expected = jala.Test.getValue(arguments, argsExpected, 1);
try {
func();
} catch (e) {
var isExpected = false;
var thrown = e;
if (expected == null) {
// didn't expect an exception, so accept everything
isExpected = true;
} else if (expected != null && e != null) {
// check if exception is the one expected
switch (typeof(expected)) {
case "string":
isExpected = (e.name === expected || e === expected);
break;
case "function":
// this is true for all JS constructors and Java classes!
isExpected = (e instanceof expected ||
(thrown = e.rhinoException) instanceof expected ||
(thrown = e.javaException) instanceof expected);
break;
case "number":
case "boolean":
default:
isExpected = (e === expected);
break;
}
}
if (!isExpected) {
throw new jala.Test.TestException(comment, "Expected " + jala.Test.valueToString(expected) +
" being thrown, but got '" + jala.Test.valueToString(thrown) + "' instead");
}
return;
}
var msg;
if (expected != null) {
msg = "Expected exception " + jala.Test.valueToString(expected) + " being thrown";
} else {
msg = "Expected an exception being thrown";
}
throw new jala.Test.TestException(comment, msg);
return;
};
/*********************************
***** H T T P C L I E N T *****
*********************************/
/**
* Constructs a new HttpClient instance
* @class Instances of this class represent a http client useable for
* testing, as any session cookies received by the tested application
* are stored and used for subsequent requests, allowing simple "walkthrough"
* tests.
* @returns A newly constructed HttpClient
* @constructor
*/
jala.Test.HttpClient = function() {
var client = new helma.Http();
var cookies = null;
/**
* Returns the http client used
* @return The http client used
* @type helma.Http
*/
this.getClient = function() {
return client;
};
/**
* Sets the cookie to use for subsequent requests using this client
* @param {Array} arr The cookie object as received from helma.Http.getUrl
*/
this.setCookies = function(arr) {
cookies = arr;
return;
};
/**
* Returns the cookies set for this http client
* @returns The cookies to use for subsequent requests
* @type Array
*/
this.getCookies = function() {
return cookies;
};
return this;
};
/**
* Sends a HTTP request to the Url passed as argument
* @param {String} method The HTTP method to use
* @param {String} url The url to request
* @param {Object} param A parameter object to use with this request
* @return An object containing response values
* @see helma.Http.prototype.getUrl
*/
jala.Test.HttpClient.prototype.executeRequest = function(method, url, param) {
var client = this.getClient();
client.setMethod(method);
client.setCookies(this.getCookies());
// prevent any caching at the remote server or any intermediate proxy
client.setHeader("Cache-control", "no-cache,max-age=0");
if (method !== 'DELETE') {
client.setContent(param);
} else {
client.setContent(null);
}
// disable following redirects, since cookies would get lost
// instead, handle a resulting redirect manually
client.setFollowRedirects(false);
var result = client.getUrl(url);
if (result.cookies != null) {
this.setCookies(result.cookies);
}
if (result.code >= 301 && result.code <= 303 && result.location != null) {
// received a redirect location, so follow it
result = this.executeRequest("GET", result.location);
}
return result;
};
/**
* Convenience method for requesting the url passed as argument
* using method GET
* @param {String} url The url to request
* @param {Object} param A parameter object to use with this request
* @return An object containing response values
* @see helma.Http.prototype.getUrl
*/
jala.Test.HttpClient.prototype.getUrl = function(url, param) {
return this.executeRequest("GET", url, param);
};
/**
* Convenience method for submitting a form.
* @param {String} url The url to request
* @param {Object} param A parameter object to use with this request
* @return An object containing response values
* @see helma.Http.prototype.getUrl
*/
jala.Test.HttpClient.prototype.submitForm = function(url, param) {
return this.executeRequest("POST", url, param);
};
/** @ignore */
jala.Test.HttpClient.prototype.toString = function() {
return "[jala.Test.HttpClient]";
};
/*****************************************************
***** T E S T D A T A B A S E M A N A G E R *****
*****************************************************/
/**
* Returns a newly created DatabaseMgr instance
* @class Instances of this class allow managing test databases
* and switching a running application to an in-memory test
* database to use within a unit test.
* @returns A newly created instance of DatabaseMgr
* @constructor
*/
jala.Test.DatabaseMgr = function() {
/**
* Map containing all test databases
*/
this.databases = {};
/**
* Map containing the original datasource
* properties that were temporarily deactivated
* by activating a test database
*/
this.dbSourceProperties = {};
return this;
};
jala.Test.DatabaseMgr.prototype.toString = function() {
return "[jala.Test DatabaseMgr]";
};
/**
* Returns a newly initialized in-memory test database with the given name
* @param {String} name The name of the test database
* @returns The newly initialized test database
* @type jala.db.RamDatabase
*/
jala.Test.DatabaseMgr.prototype.initDatabase = function(name) {
return new jala.db.RamDatabase(name);
};
/**
* Switches the application to the test database passed as argument.
* In addition this method clears the application cache and invalidates
* the root object.
* @param {jala.db.RamDatabase} testDb The test database to switch to.
* @param {String} dbSourceName Optional name of the application's database
* source that will be replaced by the test database.
*/
jala.Test.DatabaseMgr.prototype.switchToDatabase = function(testDb, dbSourceName) {
var dbName = dbSourceName || testDb.getName();
// switch the datasource to the test database
var dbSource = app.getDbSource(dbName);
var oldProps = dbSource.switchProperties(testDb.getProperties());
// store the old db properties in this manager for use in stopAll()
this.databases[dbName] = testDb;
this.dbSourceProperties[dbName] = oldProps;
// clear the application cache and invalidate root
app.clearCache();
root.invalidate();
return;
};
/**
* Switches the application datasource with the given name
* to a newly created in-memory database. In addition this method
* also clears the application cache and invalidates the root
* object to ensure that everything is re-retrieved from the
* test database.
* This method can be called with a second boolean argument indicating
* that tables used by the application should be created in the in-memory
* database (excluding any data).
* @param {String} dbSourceName The name of the application database
* source as defined in db.properties
* @param {Boolean} copyTables If true this method also copies all
* tables in the source database to the test database (excluding
* indexes).
* @param {Array} tables An optional array containing table names that
* should be created in the test database. If not specified this method
* collects all tables that are mapped in the application.
* @returns The test database
* @type jala.db.RamDatabase
*/
jala.Test.DatabaseMgr.prototype.startDatabase = function(dbSourceName, copyTables, tables) {
try {
var testDb = this.initDatabase(dbSourceName);
// switch the datasource to the test database
var dbSource = app.getDbSource(dbSourceName);
if (copyTables === true) {
if (tables === null || tables === undefined) {
// collect the table names of all relational prototypes
tables = [];
var protos = app.getPrototypes();
for (let proto of protos) {
var dbMap = proto.getDbMapping();
if (dbMap.isRelational()) {
tables.push(dbMap.getTableName());
}
}
}
testDb.copyTables(dbSource, tables);
}
this.switchToDatabase(testDb);
return testDb;
} catch (e) {
throw new jala.Test.EvaluatorException("Unable to switch to test database because of ", e);
}
};
/**
* Stops all registered test databases and and reverts the application
* to its original datasource(s) as defined in db.properties file.
* In addition this method rolls back all pending changes within the request,
* clears the application cache and invalidates the root object
* to ensure no traces of the test database are left behind.
*/
jala.Test.DatabaseMgr.prototype.stopAll = function() {
// throw away all pending transactions
res.rollback();
try {
// loop over all registered databases and revert the appropriate
// datasource back to the original database
var testDb, dbSource;
for (var dbSourceName in this.databases) {
testDb = this.databases[dbSourceName];
dbSource = app.getDbSource(dbSourceName);
dbSource.switchProperties(this.dbSourceProperties[dbSourceName]);
testDb.shutdown();
}
// clear the application cache and invalidate root
app.clearCache();
root.invalidate();
} catch (e) {
throw new jala.Test.EvaluatorException("Unable to stop test databases because of ", e);
}
return;
};
/*********************************
***** S M T P S E R V E R *****
*********************************/
/**
* Creates a new SmtpServer instance
* @class Instances of this class represent an SMTP server listening on
* localhost. By default jala.Test will create a global variable called
* "smtpServer" that contains an instance of this class. To use the server call
* {@link #start} in a test method (eg. in the basic setup method) and
* {@link #stop} in the cleanup method.
* @param {Number} port Optional port to listen on (defaults to 25)
* @returns A newly created SmtpServer instance
* @constructor
*/
jala.Test.SmtpServer = function(port) {
var server = null;
var oldSmtpServer = null;
/**
* Starts the SMTP server. Note that this method switches the SMTP server as
* defined in app.properties of the tested application or server.properties
* to "localhost" to ensure that all mails sent during tests are received
* by this server. The SMTP server definition is switched back to the
* original when {@link #stop} is called.
*/
this.start = function() {
server = new Packages.org.subethamail.wiser.Wiser()
// listen only on localhost
server.setHostname("localhost");
if (port != null && !isNaN(port)) {
server.setPort(port);
}
// switch smtp property of tested application
oldSmtpServer = getProperty("smtp");
app.__app__.getProperties().put("smtp", "localhost");
server.start();
return;
};
/**
* Stops the SMTP server and switches the "smtp" property of the tested
* application back to the value defined in app or server.properties
*/
this.stop = function() {
server.stop();
server = null;
// switch back to original SMTP server address
var props = app.__app__.getProperties();
if (oldSmtpServer != null) {
props.put("smtp", oldSmtpServer);
} else {
props.remove("smtp");
}
return;
};
/**
* Returns an array containing all mails received by the server,
* where each one is an instance of {@link jala.Test.SmtpServer.Message}
* @returns An array with all messages
* @type Array
*/
this.getMessages = function() {
var it = server.getMessages().listIterator();
var result = [];
while (it.hasNext()) {
result.push(new jala.Test.SmtpServer.Message(it.next()));
}
return result;
};
return this;
};
/** @ignore */
jala.Test.SmtpServer.prototype.toString = function() {
return "[Jala Test SmtpServer]";
};
/**
* Creates a new Mail instance
* @class Instances of this class represent a mail message received
* by the SMTP server
* @param {org.subethamail.wiser.WiserMessage} message The message
* as received by the SMTP server
* @returns A newly created Mail instance
* @constructor
*/
jala.Test.SmtpServer.Message = function(message) {
/**
* The wrapped message as MimeMessage instance
* @type javax.mail.internet.MimeMessage
* @private
*/
var mimeMessage = message.getMimeMessage();
/**
* Returns the wrapped message
* @type org.subethamail.wiser.WiserMessage
*/
this.getMessage = function() {
return message;
};
/**
* Returns the wrapped message as MimeMessage
* @type javax.mail.internet.MimeMessage
*/
this.getMimeMessage = function() {
return mimeMessage;
};
return this;
};
/** @ignore */
jala.Test.SmtpServer.Message.prototype.toString = function() {
return "[Jala Test Mail]";
};
/**
* Returns an array containing all senders of this mail
* @returns An array with all senders of this mail
* @type Array
*/
jala.Test.SmtpServer.Message.prototype.getFrom = function() {
var result = [];
this.getMimeMessage().getFrom().forEach(function(addr) {
result.push(addr.toString())
});
return result;
};
/**
* Returns an array containing all recipients of this mail
* @returns An array with all recipients of this mail
* @type Array
*/
jala.Test.SmtpServer.Message.prototype.getTo = function() {
var type = Packages.javax.mail.internet.MimeMessage.RecipientType.TO;
var result = [];
this.getMimeMessage().getRecipients(type).forEach(function(addr) {
result.push(addr.toString())
});
return result;
};
/**
* Returns an array containing all CC recipients of this mail
* @returns An array with all CC recipients of this mail
* @type Array
*/
jala.Test.SmtpServer.Message.prototype.getCc = function() {
var type = Packages.javax.mail.internet.MimeMessage.RecipientType.CC;
var result = [];
this.getMimeMessage().getRecipients(type).forEach(function(addr) {
result.push(addr.toString())
});
return result;
};
/**
* Returns an array with all reply-to addresses of this mail
* @returns An array with all reply-to addresses of this mail
* @type Array
*/
jala.Test.SmtpServer.Message.prototype.getReplyTo = function() {
var result = [];
this.getMimeMessage().getReplyTo().forEach(function(addr) {
result.push(addr.toString())
});
return result;
};
/**
* Returns the encoding of this mail as defined in the "Content-Transfer-Encoding"
* header field
* @returns The encoding of this mail
* @type String
*/
jala.Test.SmtpServer.Message.prototype.getEncoding = function() {
return this.getMimeMessage().getEncoding();
};
/**
* Returns the subject of this mail
* @returns The subject of this mail
* @type String
*/
jala.Test.SmtpServer.Message.prototype.getSubject = function() {
return this.getMimeMessage().getSubject();
};
/**
* Returns the content of this mail
* @returns The content of this mail
*/
jala.Test.SmtpServer.Message.prototype.getContent = function() {
return this.getMimeMessage().getContent();
};
/**
* Returns the content type of this mail as defined in the "Content-Type"
* header field
* @returns The content type of this mail
* @type String
*/
jala.Test.SmtpServer.Message.prototype.getContentType = function() {
return this.getMimeMessage().getContentType();
};