mas01mj@732: package asunit.framework {
mas01mj@732: import flash.display.DisplayObject;
mas01mj@732: import flash.display.DisplayObjectContainer;
mas01mj@732: import flash.errors.IllegalOperationError;
mas01mj@732: import flash.events.Event;
mas01mj@732: import flash.utils.describeType;
mas01mj@732: import flash.utils.getDefinitionByName;
mas01mj@732: import flash.utils.setTimeout;
mas01mj@732:
mas01mj@732: import asunit.errors.AssertionFailedError;
mas01mj@732: import asunit.util.ArrayIterator;
mas01mj@732: import asunit.util.Iterator;
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * A test case defines the fixture to run multiple tests. To define a test case
mas01mj@732: * 1) implement a subclass of TestCase
mas01mj@732: * 2) define instance variables that store the state of the fixture
mas01mj@732: * 3) initialize the fixture state by overriding setUp
mas01mj@732: * 4) clean-up after a test by overriding tearDown
.
mas01mj@732: * Each test runs in its own fixture so there
mas01mj@732: * can be no side effects among test runs.
mas01mj@732: * Here is an example:
mas01mj@732: *
assertTrue
with a boolean, or assertEquals
mas01mj@732: * with two primitive values that should match.
mas01mj@732: * testMethod
constructor parameter is how we
mas01mj@732: * create and run a single test case and test method.
mas01mj@732: */
mas01mj@732: public function TestCase(testMethod:String = null) {
mas01mj@732: var description:XML = describeType(this);
mas01mj@732: var className:Object = description.@name;
mas01mj@732: var methods:XMLList = description..method.((@name+"").match("^test"));
mas01mj@732: if(testMethod != null) {
mas01mj@732: testMethods = testMethod.split(", ").join(",").split(",");
mas01mj@732: if(testMethods.length == 1) {
mas01mj@732: runSingle = true;
mas01mj@732: }
mas01mj@732: } else {
mas01mj@732: setTestMethods(methods);
mas01mj@732: }
mas01mj@732: setName(className.toString());
mas01mj@732: resolveLayoutManager();
mas01mj@732: asyncQueue = [];
mas01mj@732: }
mas01mj@732:
mas01mj@732: private function resolveLayoutManager():void {
mas01mj@732: // Avoid creating import dependencies on flex framework
mas01mj@732: // If you have the framework.swc in your classpath,
mas01mj@732: // the layout manager will be found, if not, a mcok
mas01mj@732: // will be used.
mas01mj@732: try {
mas01mj@732: var manager:Class = getDefinitionByName("mx.managers.LayoutManager") as Class;
mas01mj@732: layoutManager = manager["getInstance"]();
mas01mj@732: if(!layoutManager.hasOwnProperty("resetAll")) {
mas01mj@732: throw new Error("TestCase :: mx.managers.LayoutManager missing resetAll method");
mas01mj@732: }
mas01mj@732: }
mas01mj@732: catch(e:Error) {
mas01mj@732: layoutManager = new Object();
mas01mj@732: layoutManager.resetAll = function():void {
mas01mj@732: };
mas01mj@732: }
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Sets the name of a TestCase
mas01mj@732: * @param name The name to set
mas01mj@732: */
mas01mj@732: public function setName(name:String):void {
mas01mj@732: fName = name;
mas01mj@732: }
mas01mj@732:
mas01mj@732: protected function setTestMethods(methodNodes:XMLList):void {
mas01mj@732: testMethods = new Array();
mas01mj@732: var methodNames:Object = methodNodes.@name;
mas01mj@732: var name:String;
mas01mj@732: for each(var item:Object in methodNames) {
mas01mj@732: name = item.toString();
mas01mj@732: testMethods.push(name);
mas01mj@732: }
mas01mj@732: }
mas01mj@732:
mas01mj@732: public function getTestMethods():Array {
mas01mj@732: return testMethods;
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Counts the number of test cases executed by run(TestResult result).
mas01mj@732: */
mas01mj@732: public function countTestCases():int {
mas01mj@732: return testMethods.length;
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Creates a default TestResult object
mas01mj@732: *
mas01mj@732: * @see TestResult
mas01mj@732: */
mas01mj@732: protected function createResult():TestResult {
mas01mj@732: return new TestResult();
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * A convenience method to run this test, collecting the results with
mas01mj@732: * either the TestResult provided or a default, new TestResult object.
mas01mj@732: * Expects either:
mas01mj@732: * run():void // will return the newly created TestResult
mas01mj@732: * run(result:TestResult):TestResult // will use the TestResult
mas01mj@732: * that was passed in.
mas01mj@732: *
mas01mj@732: * @see TestResult
mas01mj@732: */
mas01mj@732: public function run():void {
mas01mj@732: getResult().run(this);
mas01mj@732: }
mas01mj@732:
mas01mj@732: public function setResult(result:TestListener):void {
mas01mj@732: this.result = result;
mas01mj@732: }
mas01mj@732:
mas01mj@732: internal function getResult():TestListener {
mas01mj@732: return (result == null) ? createResult() : result;
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Runs the bare test sequence.
mas01mj@732: * @throws Error if any exception is thrown
mas01mj@732: */
mas01mj@732: public function runBare():void {
mas01mj@732: if(isComplete) {
mas01mj@732: return;
mas01mj@732: }
mas01mj@732: var name:String;
mas01mj@732: var itr:Iterator = getMethodIterator();
mas01mj@732: if(itr.hasNext()) {
mas01mj@732: name = String(itr.next());
mas01mj@732: currentState = PRE_SET_UP;
mas01mj@732: runMethod(name);
mas01mj@732: }
mas01mj@732: else {
mas01mj@732: cleanUp();
mas01mj@732: getResult().endTest(this);
mas01mj@732: isComplete = true;
mas01mj@732: dispatchEvent(new Event(Event.COMPLETE));
mas01mj@732: }
mas01mj@732: }
mas01mj@732:
mas01mj@732: private function getMethodIterator():Iterator {
mas01mj@732: if(methodIterator == null) {
mas01mj@732: methodIterator = new ArrayIterator(testMethods);
mas01mj@732: }
mas01mj@732: return methodIterator;
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Override this method in Asynchronous test cases
mas01mj@732: * or any other time you want to perform additional
mas01mj@732: * member cleanup after all test methods have run
mas01mj@732: **/
mas01mj@732: protected function cleanUp():void {
mas01mj@732: }
mas01mj@732:
mas01mj@732: private function runMethod(methodName:String):void {
mas01mj@732: try {
mas01mj@732: if(currentState == PRE_SET_UP) {
mas01mj@732: currentState = SET_UP;
mas01mj@732: getResult().startTestMethod(this, methodName);
mas01mj@732: setUp(); // setUp may be async and change the state of methodIsAsynchronous
mas01mj@732: }
mas01mj@732: currentMethod = methodName;
mas01mj@732: if(!waitForAsync()) {
mas01mj@732: currentState = RUN_METHOD;
mas01mj@732: this[methodName]();
mas01mj@732: }
mas01mj@732: }
mas01mj@732: catch(assertionFailedError:AssertionFailedError) {
mas01mj@732: getResult().addFailure(this, assertionFailedError);
mas01mj@732: }
mas01mj@732: catch(unknownError:Error) {
mas01mj@732: getResult().addError(this, unknownError);
mas01mj@732: }
mas01mj@732: finally {
mas01mj@732: if(!waitForAsync()) {
mas01mj@732: runTearDown();
mas01mj@732: }
mas01mj@732: }
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Sets up the fixture, for example, instantiate a mock object.
mas01mj@732: * This method is called before each test is executed.
mas01mj@732: * throws Exception on error.
mas01mj@732: *
mas01mj@732: * @example This method is usually overridden in your concrete test cases:
mas01mj@732: * TestCase
doesn't do anything on tearDown
,
mas01mj@732: * It's a good idea to call super.tearDown()
in your subclasses. Many projects
mas01mj@732: * wind up using some common fixtures which can often be extracted out a common project
mas01mj@732: * TestCase
.
mas01mj@732: *
mas01mj@732: * tearDown
is not called when we tell a test case to execute
mas01mj@732: * a single test method.
mas01mj@732: *
mas01mj@732: * @throws Error on error.
mas01mj@732: *
mas01mj@732: * @example This method is usually overridden in your concrete test cases:
mas01mj@732: * DisplayObjectContainer
that will be used by
mas01mj@732: * addChild
and removeChild
helper methods.
mas01mj@732: **/
mas01mj@732: public function getContext():DisplayObjectContainer {
mas01mj@732: return context;
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Called from within setUp
or the body of any test method.
mas01mj@732: *
mas01mj@732: * Any call to addAsync
, will prevent test execution from continuing
mas01mj@732: * until the duration
(in milliseconds) is exceeded, or the function returned by addAsync
mas01mj@732: * is called. addAsync
can be called any number of times within a particular
mas01mj@732: * test method, and will block execution until each handler has returned.
mas01mj@732: *
mas01mj@732: * Following is an example of how to use the addAsync
feature:
mas01mj@732: * addAsync
block.
mas01mj@732: **/
mas01mj@732: protected function addAsync(handler:Function = null, duration:Number=DEFAULT_TIMEOUT, failureHandler:Function=null):Function {
mas01mj@732: if(handler == null) {
mas01mj@732: handler = function(args:*):* {return;};
mas01mj@732: }
mas01mj@732: var async:AsyncOperation = new AsyncOperation(this, handler, duration, failureHandler);
mas01mj@732: asyncQueue.push(async);
mas01mj@732: return async.getCallback();
mas01mj@732: }
mas01mj@732:
mas01mj@732: internal function asyncOperationTimeout(async:AsyncOperation, duration:Number, isError:Boolean=true):void {
mas01mj@732: if(isError) getResult().addError(this, new IllegalOperationError("TestCase.timeout (" + duration + "ms) exceeded on an asynchronous operation."));
mas01mj@732: asyncOperationComplete(async);
mas01mj@732: }
mas01mj@732:
mas01mj@732: internal function asyncOperationComplete(async:AsyncOperation):void{
mas01mj@732: // remove operation from queue
mas01mj@732: var i:int = asyncQueue.indexOf(async);
mas01mj@732: asyncQueue.splice(i,1);
mas01mj@732: // if we still need to wait, return
mas01mj@732: if(waitForAsync()) return;
mas01mj@732: if(currentState == SET_UP) {
mas01mj@732: runMethod(currentMethod);
mas01mj@732: }
mas01mj@732: else if(currentState == RUN_METHOD) {
mas01mj@732: runTearDown();
mas01mj@732: }
mas01mj@732: }
mas01mj@732:
mas01mj@732: private function waitForAsync():Boolean{
mas01mj@732: return asyncQueue.length > 0;
mas01mj@732: }
mas01mj@732:
mas01mj@732: protected function runTearDown():void {
mas01mj@732: if(currentState == TEAR_DOWN) {
mas01mj@732: return;
mas01mj@732: }
mas01mj@732: currentState = TEAR_DOWN;
mas01mj@732: if(isComplete) {
mas01mj@732: return;
mas01mj@732: }
mas01mj@732: if(!runSingle) {
mas01mj@732: getResult().endTestMethod(this, currentMethod);
mas01mj@732: tearDown();
mas01mj@732: layoutManager.resetAll();
mas01mj@732: }
mas01mj@732: setTimeout(runBare, 5);
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Helper method for testing DisplayObject
s.
mas01mj@732: *
mas01mj@732: * This method allows you to more easily add and manage DisplayObject
mas01mj@732: * instances in your TestCase
.
mas01mj@732: *
mas01mj@732: * If you are using the regular TestRunner
, you cannot add Flex classes.
mas01mj@732: *
mas01mj@732: * If you are using a FlexRunner
base class, you can add either
mas01mj@732: * regular DisplayObject
s or IUIComponent
s.
mas01mj@732: *
mas01mj@732: * Usually, this method is called within setUp
, and removeChild
mas01mj@732: * is called from within tearDown
. Using these methods, ensures that added
mas01mj@732: * children will be subsequently removed, even when tests fail.
mas01mj@732: *
mas01mj@732: * Here is an example of the addChild
method:
mas01mj@732: * DisplayObject
s.
mas01mj@732: *
mas01mj@732: * Update: This method should no longer fail if the provided DisplayObject
mas01mj@732: * has already been removed.
mas01mj@732: **/
mas01mj@732: protected function removeChild(child:DisplayObject):DisplayObject {
mas01mj@732: if(child == null) {
mas01mj@732: return null;
mas01mj@732: }
mas01mj@732: try {
mas01mj@732: return getContext().removeChild(child);
mas01mj@732: }
mas01mj@732: catch(e:Error) {
mas01mj@732: }
mas01mj@732: return null;
mas01mj@732: }
mas01mj@732: }
mas01mj@732: }