Wednesday, April 20, 2011

Darren on Flex: FlexUnit4 Testing with Parsley - Improved Parsley Support with FlexUnit's [RunWith(...)] & [Rule]

In Part-3 I showed how a DSL can be used to reduce the complexity of asynchronous testing when using the Fluint Sequence API. With this DSL I was also able to remove several lines of boilerplate code. These might be modest improvements, but the introduction of a DSL paves the way for more concise tests.

In Part-4, I'll show how to integrate Parsley into FlexUnit by using the [RunWith] and [Rule] hook mechanisms to make FlexUnit do the heavy lifting i.e. context initialization and dependency injection.

The 'test' code re-re-revisted...

As mentioned there are two approaches, [RunWith] and [Rule]. I had previously held-back from exploring the [Rule] option as it comes with FlexUnit 4.1, which up until very recently, had only been available in beta. I explore this second option later in this post, but first...
The [RunWith] approach
package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.GetPersonCommandComponentTestContext;GetPersonCommandComponentTestContext;
    import com.darrenbishop.support.flexunit.parsley.ParsleyRunner;ParsleyRunner;
    
    import org.flexunit.asserts.fail;
    import flash.events.ErrorEvent;
    import org.flexunit.assertThat;
    import org.hamcrest.object.equalTo;
    import com.darrenbishop.support.create;
    import com.darrenbishop.crm.directory.domain.Person;
    import com.darrenbishop.support.flexunit.async.AsyncHelper;
    import org.spicefactory.parsley.core.context.Context;
    import flash.events.EventDispatcher;
    import org.spicefactory.parsley.core.messaging.MessageProcessor;
    
    [RunWith('com.darrenbishop.support.flexunit.parsley.ParsleyRunner')]
    [Context('com.darrenbishop.crm.directory.GetPersonCommandComponentTestContext')]
    public class GetPersonCommandParsleyRunnerComponentTest extends AsyncHelper {
        [MessageDispatcher]
        public var dispatcher:Function;
        
        [Inject]
        public var context:Context;
        
        [Inject]
        public var service:StubDirectoryService;
        
        [Inject]
        public var command:GetPersonCommand;
        
        public var dodgyPerson:Person;
        
        public var soundPerson:Person;
        
        private var eventDispatcher:EventDispatcher;
        
        [Before]
        public function primeService():void {
            eventDispatcher = new EventDispatcher();
            
            dodgyPerson = create(Person, {'id': 1, 'firstname': 'John001', 'lastname': 'Smith001', 'phone': 6803225});
            soundPerson = create(Person, {'id': 2, 'firstname': 'John002', 'lastname': 'Smith002', 'phone': 6809168});
            
            service.add(dodgyPerson, soundPerson);
            
            service.dodgyPerson = dodgyPerson.id;
            service.soundPerson = soundPerson.id;
        }
        
        [MessageError(type='com.darrenbishop.crm.directory.application.PersonEvent')]
        public function rethrowIt(processor:MessageProcessor, error:Error):void { 
            eventDispatcher.dispatchEvent(toEvent(error));
        }
        
        [MessageHandler(selector='PersonEvent.found')]
        public function passItOn(event:PersonEvent):void {
            eventDispatcher.dispatchEvent(event);
        }
        
        [Test(async,description='Test GetPersonCommand result-handler does not mangle the person found.')]
        public function resultDoesNotManglePersonFound():void {
            dispatcher(PersonEvent.newLookup(soundPerson.id));
            waitFor(eventDispatcher, PersonEvent.FOUND, 1000);
            thenAssert(function(event:PersonEvent, data:*):void {
                assertThat(event.type, equalTo('PersonEvent.found'));
                assertThat(event.person.fullname, equalTo('John002 Smith002'));
            });
        }
        
        [Test(async,description='Test GetPersonCommand result-handler verifies the id of the person found.')]
        public function resultVerifiesPersonFound():void {
            dispatcher(PersonEvent.newLookup(dodgyPerson.id));
            waitFor(eventDispatcher, ErrorEvent.ERROR, 1000);
            thenAssert(function(event:ErrorEvent, data:*):void {
                assertThat(event.text, equalTo(sprintf('Found person (%d) does not match lookup person (%d)', soundPerson.id, dodgyPerson.id)));
            });
        }
    }
}
I declare the ParsleyRunner class as the default attribute to the [RunWith] metadata tag; I import and reference the same (at the top of the excerpt) to ensure the runner is linked in. This highlights one of the fundamental problems with this approach: you must repeat you intention too many times - once should be enough.

The same goes for the GetPersonCommandComponentTestContext; the runner needs to be told somehow which Parsley context to initialize. Perhaps there are better ways to communicate to the runner which context to use, with static members maybe, but the problem persists for the runner itself so there's little value in exploring such an enhancement.

The only other significant change is the [Before] annotated method; this method is no-longer responsible for building the Parsley context so I renamed it from initializeContext() to primeService(), as that's all it now does; it primes the StubDirectoryService with people and the id to trigger the error case.

Introducing the the ParsleyRunner...
package com.darrenbishop.support.flexunit.parsley {
    import flash.utils.getDefinitionByName;
    import flash.utils.getQualifiedClassName;
    
    import flex.lang.reflect.metadata.MetaDataAnnotation;
    
    import org.flexunit.internals.runners.statements.IAsyncStatement;
    import org.flexunit.internals.runners.statements.StatementSequencer;
    import org.flexunit.runner.notification.IRunNotifier;
    import org.flexunit.runners.BlockFlexUnit4ClassRunner;
    import org.flexunit.runners.model.FrameworkMethod;

    public class ParsleyRunner extends BlockFlexUnit4ClassRunner {
        public static const CONTEXT_METADATA_NAME:String = 'Context';
        
        private var contextDescriptor:Class;
        
        public function ParsleyRunner(cls:Class) {
            super(cls);
            var ctxMetaData:MetaDataAnnotation = testClass.klassInfo.getMetaData(CONTEXT_METADATA_NAME);
            this.contextDescriptor = classFor(ctxMetaData.defaultArgument.key)
        }
        
        private function classFor(name:String):Class {
            return Class(getDefinitionByName(getQualifiedClassName(name)));
        }
        
        private function withParsleyContextBuilt(test:Object):IAsyncStatement {
            return new BuildParsleyContext(test, contextDescriptor);
        }

        protected override function withBefores(method:FrameworkMethod, test:Object, statement:IAsyncStatement):IAsyncStatement {
            if (contextDescriptor) {
                var sequencer:StatementSequencer = new StatementSequencer();
                sequencer.addStep(withParsleyContextBuilt(test));
                sequencer.addStep(super.withBefores(method, test, statement));
                return sequencer;
            }
            else {
                return super.withBefores(method, test, statement);
            }
        }
    }
}
I determine the class that describes the Parsley context for this test in the constructor, by retrieving the default attribute value of the [Context] metadata tag; this I consider to be a one-off class-level operation. The withBefores(...) template method is invoked before each test method, to contribute to the preparation of the environment in which the test method will execute; this method delegates to withParsleyContextBuilt(...) to generate a BuildParsleyContext command-object. BuildParsleyContext is not a Parsley command, as in the one being tested - it's just the pattern/convention used in the FlexUnit internals.
BuildParsleyContext, the heavy lifter
This command-object does all the work using the Parsley ActionScript3 API for context initialization:
package com.darrenbishop.support.flexunit.parsley {
    import flash.utils.getDefinitionByName;
    
    import org.flexunit.internals.runners.statements.AsyncStatementBase;
    import org.flexunit.internals.runners.statements.IAsyncStatement;
    import org.flexunit.token.AsyncTestToken;
    import org.spicefactory.parsley.core.context.Context;
    import org.spicefactory.parsley.flex.FlexContextBuilder;

    public class BuildParsleyContext extends AsyncStatementBase implements IAsyncStatement {
        protected var test:Object;
        protected var contextDescriptor:Class;
        
        public function BuildParsleyContext(target:Object, contextDescriptor:Class) {
            this.test = target;
            this.contextDescriptor = contextDescriptor;
        }
        
        public function evaluate(parentToken:AsyncTestToken):void {
            buildContext(parentToken);
        }

        private function buildContext(parentToken:AsyncTestToken):void {
            var error:Error = null;
            try {
                var ctx:Context = FlexContextBuilder.build(contextDescriptor);
                ctx.createDynamicContext().addObject(test);
            }
            catch (e:Error) {
                error = e;
            }
            
            parentToken.sendResult(error);
        }
    }
}
Basically, what once lived and would have been replicated in the [Before] methods of each and every Parsley-powered test, now lives here; but again, there's nothing new, just a relocation of code to somewhere more appropriate for re-use.

And that's it for the [RunWith] approach, but now with the relase of FlexUnit-4.1, we have...

The [Rule] approach
This hook mechanism completely solves the problem of having to declare the runner implementation class as a string in a metadata attribute:
package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.GetPersonCommandComponentTestContext;
    import com.darrenbishop.crm.directory.domain.Person;
    import com.darrenbishop.support.create;
    import com.darrenbishop.support.flexunit.async.AsyncHelper;
    import com.darrenbishop.support.flexunit.parsley.ParsleyRule;
    
    import flash.events.ErrorEvent;
    import flash.events.EventDispatcher;
    
    import org.flexunit.assertThat;
    import org.hamcrest.object.equalTo;
    import org.spicefactory.parsley.core.messaging.MessageProcessor;
    
    public class GetPersonCommandParsleyRuleComponentTest extends AsyncHelper {
        [Rule]
        public var rule:ParsleyRule = new ParsleyRule(GetPersonCommandComponentTestContext);
        
        [MessageDispatcher]
        public var dispatcher:Function;
        
        [Inject]
        public var service:StubDirectoryService;
        
        [Inject]
        public var command:GetPersonCommand;
        
        public var dodgyPerson:Person;
        
        public var soundPerson:Person;
        
        private var eventDispatcher:EventDispatcher;
        
        [Before]
        public function primeService():void {
            eventDispatcher = new EventDispatcher();
            
            dodgyPerson = create(Person, {'id': 1, 'firstname': 'John001', 'lastname': 'Smith001', 'phone': 6803225});
            soundPerson = create(Person, {'id': 2, 'firstname': 'John002', 'lastname': 'Smith002', 'phone': 6809168});
            
            service.add(dodgyPerson, soundPerson);
            
            service.dodgyPerson = dodgyPerson.id;
            service.soundPerson = soundPerson.id;
        }
        
        [MessageError(type='com.darrenbishop.crm.directory.application.PersonEvent')]
        public function rethrowIt(processor:MessageProcessor, error:Error):void { 
            eventDispatcher.dispatchEvent(toEvent(error));
        }
        
        [MessageHandler(selector='PersonEvent.found')]
        public function passItOn(event:PersonEvent):void {
            eventDispatcher.dispatchEvent(event);
        }
        
        [Test(async,description='Test GetPersonCommand result-handler does not mangle the person found.')]
        public function resultDoesNotManglePersonFound():void {
            dispatcher(PersonEvent.newLookup(soundPerson.id));
            waitFor(eventDispatcher, PersonEvent.FOUND, 1000);
            thenAssert(function(event:PersonEvent, data:*):void {
                assertThat(event.type, equalTo('PersonEvent.found'));
                assertThat(event.person.fullname, equalTo('John002 Smith002'));
            });
        }
        
        [Test(async,description='Test GetPersonCommand result-handler verifies the id of the person found.')]
        public function resultVerifiesPersonFound():void {
            dispatcher(PersonEvent.newLookup(dodgyPerson.id));
            waitFor(eventDispatcher, ErrorEvent.ERROR, 1000);
            thenAssert(function(event:ErrorEvent, data:*):void {
                assertThat(event.text, equalTo(sprintf('Found person (%d) does not match lookup person (%d)', soundPerson.id, dodgyPerson.id)));
            });
        }
    }
}
The shift can be simply thought of as declaring the runner implementation as a member variable i.e. rule and annotating it with the [Rule] metadata tag, rather than as a string metadata attribute. Of course, now it's no longer a runner, but a rule and extends a different parent class and implements slightly different template methods... but for the most part it's the same.
The ParsleyRule to rule it all
As mentioned there are few differences, but one welcome difference, now that the rule is declared and initialized as an instance variable, is that in doing so the constructor is called and any arbitrary arguments can be passed in. I use this to communicate which Parsley context to initialize, by passing in the class that defines it.
package com.darrenbishop.support.flexunit.parsley {
    import org.flexunit.internals.runners.statements.IAsyncStatement;
    import org.flexunit.internals.runners.statements.MethodRuleBase;
    import org.flexunit.internals.runners.statements.StatementSequencer;
    import org.flexunit.rules.IMethodRule;
    import org.flexunit.runners.model.FrameworkMethod;
    import org.flexunit.token.AsyncTestToken;

    public class ParsleyRule extends MethodRuleBase implements IMethodRule {
        private var contextDescriptor:Class;
        
        public function ParsleyRule(contextDescriptor:Class) {
            super();
            this.contextDescriptor = contextDescriptor;
        }
        
        override public function apply(base:IAsyncStatement, method:FrameworkMethod, test:Object):IAsyncStatement {
            var sequencer:StatementSequencer = new StatementSequencer();
            sequencer.addStep(withParsleyContextBuilt(test));
            sequencer.addStep(base);
            return super.apply(sequencer, method, test);
        }
        
        private function withParsleyContextBuilt(test:Object):IAsyncStatement {
            return new BuildParsleyContext(test, contextDescriptor);
        }
        
        override public function evaluate(parentToken:AsyncTestToken):void {
            super.evaluate(parentToken);
            proceedToNextStatement();
        }
    }
}
The [Rule] approach has the benefit that it does not exhaust the use of inheritance, nor does it use [RunWith], both of which are use-once mechanisms.

There is now the benefit that multiple rules can be defined and used in conjunction with each other; this removes the impediments to mocking described in Part-1

I have actually succeeded in mocking a DirectoryService instance and embedding that instance into the Parsley context ready for dependency-injection, but that's another blog :-)

What's Next...

Part 5: Testing with a Parsley-Aware DSL
The final (planned) part of this blog series will demonstrate the ParsleyHelper base class. ParsleyHelper extends from AsyncHelper, introduced in Part-3, to add a layer of abstraction for all the Parsley bits and pieces; this improved DSL implementation allows for the removal of all reference to anything-Parsley

... except for the ParsleyRule

... and the ParsleyHelper.

Tuesday, April 19, 2011

Darren on Flex: FlexUnit4 Testing with Parsley - Hiding Fluint Sequences with a Flow-based DSL

In Part-2 I showed how to implement a component-test, with some of the setup delegated to Parsley to leverage IoC and DI.

In Part-3, I'll show how to hide the Fluint Sequence API, used for asynchronous testing, behind an embedded asynchronous-DSL. The DSL design presented only really serves to seed an idea; anyone can implement a DSL, using different verb-names for methods and statement construction.

The 'test' code re-revisted...

package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.GetPersonCommandComponentTestContext;
    import com.darrenbishop.crm.directory.domain.Person;
    import com.darrenbishop.support.create;
    import com.darrenbishop.support.flexunit.async.AsyncHelper;
    
    import flash.events.ErrorEvent;
    import flash.events.EventDispatcher;
    
    import org.flexunit.assertThat;
    import org.hamcrest.object.equalTo;
    import org.spicefactory.parsley.core.context.Context;
    import org.spicefactory.parsley.core.messaging.MessageProcessor;
    import org.spicefactory.parsley.flex.FlexContextBuilder;
    
    public class GetPersonCommandDSLComponentTest extends AsyncHelper {
        [MessageDispatcher]
        public var dispatcher:Function;
        
        [Inject]
        public var context:Context;
        
        [Inject]
        public var service:StubDirectoryService;
        
        [Inject]
        public var command:GetPersonCommand;
        
        public var dodgyPerson:Person;
        
        public var soundPerson:Person;
        
        private var eventDispatcher:EventDispatcher;
        
        [Before]
        public function initializeContext():void {
            var ctx:Context = FlexContextBuilder.build(GetPersonCommandComponentTestContext);
            ctx.createDynamicContext().addObject(this);
            
            eventDispatcher = new EventDispatcher();
            
            dodgyPerson = create(Person, {'id': 1, 'firstname': 'John001', 'lastname': 'Smith001', 'phone': 6803225});
            soundPerson = create(Person, {'id': 2, 'firstname': 'John002', 'lastname': 'Smith002', 'phone': 6809168});
            
            service.add(dodgyPerson, soundPerson);
            
            service.dodgyPerson = dodgyPerson.id;
            service.soundPerson = soundPerson.id;
        }
        
        [After]
        public function destroyContext():void {
            context.destroy();
            eventDispatcher = null;
        }
        
        [MessageError(type='com.darrenbishop.crm.directory.application.PersonEvent')]
        public function rethrowIt(processor:MessageProcessor, error:Error):void { 
            eventDispatcher.dispatchEvent(toEvent(error));
        }
        
        [MessageHandler(selector='PersonEvent.found')]
        public function passItOn(event:PersonEvent):void {
            eventDispatcher.dispatchEvent(event);
        }
        
        [Test(async,description='Test GetPersonCommand result-handler does not mangle the person found.')]
        public function resultDoesNotManglePersonFound():void {
            dispatcher(PersonEvent.newLookup(soundPerson.id));
            waitFor(eventDispatcher, PersonEvent.FOUND, 1000);
            thenAssert(function(event:PersonEvent, data:*):void {
                assertThat(event.type, equalTo('PersonEvent.found'));
                assertThat(event.person.fullname, equalTo('John002 Smith002'));
            });
        }
        
        [Test(async,description='Test GetPersonCommand result-handler verifies the id of the person found.')]
        public function resultVerifiesPersonFound():void {
            dispatcher(PersonEvent.newLookup(dodgyPerson.id));
            waitFor(eventDispatcher, ErrorEvent.ERROR, 1000);
            thenAssert(function(event:ErrorEvent, data:*):void {
                assertThat(event.text, equalTo(sprintf('Found person (%d) does not match lookup person (%d)', soundPerson.id, dodgyPerson.id)));
            });
        }
    }
}
Note that the test class now extends AsyncHelper, that is, I use inheritance to provide access to the async-DSL methods. An alternative approach is that adopted by Mockito-Flex, where a global object is used as/to share state between a suite of global functions implementing the mocking-DSL. I believe this is a safe approach as the Flex execution model is 'single-threaded'... I might explore this approach and free-up inheritance for local uses.

Anyway, my main objective in doing this is to remove any explicit use of the Fluint Sequence API and provide an abstraction that is a bit easier to use and read. To make the refactorings and changes a little more clear, I've provided a diff below generated against the test above and that from Part-2

--- C:/Dev/workspaces/flex/parsley-flexunit/src/test/flex/com/darrenbishop/crm/directory/application/GetPersonCommandComponentTest.as Fri Jun 17 07:26:56 2011
+++ C:/Dev/workspaces/flex/parsley-flexunit/src/test/flex/com/darrenbishop/crm/directory/application/GetPersonCommandDSLComponentTest.as Fri Jun 17 07:23:37 2011
@@ -4,2 +4,3 @@
     import com.darrenbishop.support.create;
+    import com.darrenbishop.support.flexunit.async.AsyncHelper;
     
@@ -9,4 +10,2 @@
     import org.flexunit.assertThat;
-    import org.fluint.sequence.SequenceRunner;
-    import org.fluint.sequence.SequenceWaiter;
     import org.hamcrest.object.equalTo;
@@ -16,3 +15,3 @@
     
-    public class GetPersonCommandComponentTest {
+    public class GetPersonCommandDSLComponentTest extends AsyncHelper {
         [MessageDispatcher]
@@ -35,5 +34,3 @@
         
-        private var sequence:SequenceRunner;
-        
-        [Before(async)]
+        [Before]
         public function initializeContext():void {
@@ -44,4 +41,2 @@
             
-            sequence = new SequenceRunner(this);
-            
             dodgyPerson = create(Person, {'id': 1, 'firstname': 'John001', 'lastname': 'Smith001', 'phone': 6803225});
@@ -59,8 +54,7 @@
             eventDispatcher = null;
-            sequence = null;
         }
-
+        
         [MessageError(type='com.darrenbishop.crm.directory.application.PersonEvent')]
         public function rethrowIt(processor:MessageProcessor, error:Error):void { 
-            eventDispatcher.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, error.message));
+            eventDispatcher.dispatchEvent(toEvent(error));
         }
@@ -75,6 +69,4 @@
             dispatcher(PersonEvent.newLookup(soundPerson.id));
-            
-            sequence.addStep(new SequenceWaiter(eventDispatcher, PersonEvent.FOUND, 1000));
-            
-            sequence.addAssertHandler(function(event:PersonEvent, data:*):void {
+            waitFor(eventDispatcher, PersonEvent.FOUND, 1000);
+            thenAssert(function(event:PersonEvent, data:*):void {
                 assertThat(event.type, equalTo('PersonEvent.found'));
@@ -82,4 +74,2 @@
             });
-            
-            sequence.run();
         }
@@ -89,10 +79,6 @@
             dispatcher(PersonEvent.newLookup(dodgyPerson.id));
-            
-            sequence.addStep(new SequenceWaiter(eventDispatcher, ErrorEvent.ERROR, 1000));
-            
-            sequence.addAssertHandler(function(event:ErrorEvent, data:*):void {
+            waitFor(eventDispatcher, ErrorEvent.ERROR, 1000);
+            thenAssert(function(event:ErrorEvent, data:*):void {
                 assertThat(event.text, equalTo(sprintf('Found person (%d) does not match lookup person (%d)', soundPerson.id, dodgyPerson.id)));
             });
-            
-            sequence.run();
         }
By counting the added (8) and subtracted (14) lines reveals a net reduction by 6 lines - a modest improvement. I could have indulged a bit more and come up with a nice Given, When, Then conforming DSL, but too much thought would have had to go into getting the semantics right, so I defer that for another day.

The important thing is I have achieved my goal, stated above.

async - refactored
So as discussed, all the Fluint Sequence usage has been pulled up into the AsyncHelper base class:
package com.darrenbishop.support.flexunit {
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    
    import mx.events.FlexEvent;
    
    import org.fluint.sequence.*;

    public class AsyncHelper {
        protected var sequence:SequenceRunner;
        
        [Before(async)]
        public function setUpSequence():void {
            sequence = new SequenceRunner(this);
        }
        
        [After(async)]
        public function tearDownSequence():void {
            sequence = null;
        }
        
        // Primitive steps
        
        protected function waitFor(dispatcher:EventDispatcher, eventType:String=FlexEvent.VALUE_COMMIT, timeout:Number=100):void {
            sequence.addStep(new SequenceWaiter(dispatcher, eventType, timeout));
        }
        
        protected function dispatch(dispatcher:EventDispatcher, event:Event):void {
            sequence.addStep(new SequenceEventDispatcher(dispatcher, event));
        }
        
        protected function assert(handler:Function, passThroughData:*):void {
            sequence.addAssertHandler(handler, passThroughData);
        }
        
        protected function run():void {
            sequence.run();
        }
        
        protected function toEvent(error:Error):ErrorEvent {
            return new ErrorEvent(ErrorEvent.ERROR, false, false, error.message);
        }
        
        // Composite steps
        
        protected function thenAssert(handler:Function, passThroughData:*=null):void {
            assert(handler, passThroughData || {});
            run();
        }
    }
}
Notice that this test base-class defines setup and teardown behaviour, using [Before] and [After] metadata tags. FlexUnit supports [Before] and [After] inheritance, using an accumulator strategy rather than the standard-OO override strategy:
  • [Before] annotated methods are applied from the root of the inheritance chain down, prior to a test method invocation
  • [After] annotated methods are applied from bottom to top
Presumably, this provides symmetry to the setup and teardown of state and fixtures, with each level of inheritance potentially relying on the effects of the levels above.

The AsyncHelper class is pretty self explanatory: the [Before] and [After] methods are nothing new - they just manage the existence of the sequence object. The rest of the methods just manipulate the sequence object, but again, in no new ways; they are given verb or verb-phrases for names, thus self-describing what they do.

The test author will need to know about these inherited methods to use them - not really a problem these days... we all hit Ctrl+Space, right?

The real value in DSLs comes when developers (or even non-developers, if you happen to pair with a QA guy or a BA) who are not the original author of the tests (or whatever) must read them; they will be able to discern the embedded flow/logic/intention/expectation a lot easier.

What's Next...

Part 4: Improved Parsley Support with FlexUnit's [RunWith(...)][Rule]
I'll introduce the ParsleyRunner, which facilitates integration of Parsley into FlexUnit testing, using the [RunWith] metadata tag. Also, with the recent release of FlexUnit 4.1, I'll implement improved Parsley-FlexUnit integration using the [Rule] metadata tag.

Sunday, April 17, 2011

Darren on Flex: FlexUnit4 Testing with Parsley - Asynchronous Testing with Parsley and Fluint Sequences

In Part-1 I showed a pure-unit test, completely managed outside Parsley.

In Part-2 I'll show how to implement a component-test, with some of the setup delegated to Parsley to leverage IoC and DI.

The 'test' code: revisited...

Again I'll jump straight into the test class itself
The FlexUnit4 GetPersonCommandComponentTest
package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.GetPersonCommandComponentTestContext;
    import com.darrenbishop.crm.directory.domain.Person;
    import com.darrenbishop.support.create;
    
    import flash.events.ErrorEvent;
    import flash.events.EventDispatcher;
    
    import org.flexunit.assertThat;
    import org.fluint.sequence.SequenceRunner;
    import org.fluint.sequence.SequenceWaiter;
    import org.hamcrest.object.equalTo;
    import org.spicefactory.parsley.core.context.Context;
    import org.spicefactory.parsley.core.messaging.MessageProcessor;
    import org.spicefactory.parsley.flex.FlexContextBuilder;
    
    public class GetPersonCommandComponentTest {
        [MessageDispatcher]
        public var dispatcher:Function;
        
        [Inject]
        public var context:Context;
        
        [Inject]
        public var service:StubDirectoryService;
        
        [Inject]
        public var command:GetPersonCommand;
        
        public var dodgyPerson:Person;
        
        public var soundPerson:Person;
        
        private var eventDispatcher:EventDispatcher;
        
        private var sequence:SequenceRunner;
        
        [Before(async)]
        public function initializeContext():void {
            var ctx:Context = FlexContextBuilder.build(GetPersonCommandComponentTestContext);
            ctx.createDynamicContext().addObject(this);
            
            eventDispatcher = new EventDispatcher();
            
            sequence = new SequenceRunner(this);
            
            dodgyPerson = create(Person, {'id': 1, 'firstname': 'John001', 'lastname': 'Smith001', 'phone': 6803225});
            soundPerson = create(Person, {'id': 2, 'firstname': 'John002', 'lastname': 'Smith002', 'phone': 6809168});
            
            service.add(dodgyPerson, soundPerson);
            
            service.dodgyPerson = dodgyPerson.id;
            service.soundPerson = soundPerson.id;
        }
        
        [After]
        public function destroyContext():void {
            context.destroy();
            eventDispatcher = null;
            sequence = null;
        }

        [MessageError(type='com.darrenbishop.crm.directory.application.PersonEvent')]
        public function rethrowIt(processor:MessageProcessor, error:Error):void { 
            eventDispatcher.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, error.message));
        }
        
        [MessageHandler(selector='PersonEvent.found')]
        public function passItOn(event:PersonEvent):void {
            eventDispatcher.dispatchEvent(event);
        }
        
        [Test(async,description='Test GetPersonCommand result-handler does not mangle the person found.')]
        public function resultDoesNotManglePersonFound():void {
            dispatcher(PersonEvent.newLookup(soundPerson.id));
            
            sequence.addStep(new SequenceWaiter(eventDispatcher, PersonEvent.FOUND, 1000));
            
            sequence.addAssertHandler(function(event:PersonEvent, data:*):void {
                assertThat(event.type, equalTo('PersonEvent.found'));
                assertThat(event.person.fullname, equalTo('John002 Smith002'));
            });
            
            sequence.run();
        }
        
        [Test(async,description='Test GetPersonCommand result-handler verifies the id of the person found.')]
        public function resultVerifiesPersonFound():void {
            dispatcher(PersonEvent.newLookup(dodgyPerson.id));
            
            sequence.addStep(new SequenceWaiter(eventDispatcher, ErrorEvent.ERROR, 1000));
            
            sequence.addAssertHandler(function(event:ErrorEvent, data:*):void {
                assertThat(event.text, equalTo(sprintf('Found person (%d) does not match lookup person (%d)', soundPerson.id, dodgyPerson.id)));
            });
            
            sequence.run();
        }
    }
}
There's one quick thing to point out here - this component-test is actually simpler than the unit-test discussed in Part-1, with one fewer tests as there's no value in verifying the service-call-sequencing again. Incidentally, the remaining two tests still provide 75% coverage (for the same reasons as before).

Right, I'll breeze past dispatcher, command and service; these are standard injections you'd use while programming with Parsley.

A lesser known fact about context: no declaration is required for this dependency i.e. in GetPersonCommandTestContext; Parsley will detect the expectation of an object of type org.spicefactory.parsley.core.context.Context and will inject that instance in which this is initialized by, that is, the context doing the injections will be injected.
Pretty neat, eh. I will show how to leverage this later in the series.

The next two variables, dodgyPerson and soundPerson, reflect a kind-of agreement between the StubDirectoryService implementation that is injected and the test, which is something like: if you request dodgyPerson, I will send back soundPerson; I use this to implement the negative-test.
Again, mocking would be the better approach here, where interactions would then be dictated rather than just implied... moving on...

The eventDispatcher is used here to bridge the Parsley Messaging Framework and FlexUnit (Fluint) Asynchronous Testing.

The sequence object is the key to FlexUnit (Fluint) Asynchronous Testing; it must be used with the async attribute in [Before], [After] or [Test] metadata tags.

Where the Magic Happens... Well, Some of It Anyway
initializeContext() is where the Parsley context is initialized and this test instance along with it.
        [Before(async)]
        public function initializeContext():void {
            var ctx:Context = FlexContextBuilder.build(GetPersonCommandComponentTestContext);
            ctx.createDynamicContext().addObject(this);
            
            eventDispatcher = new EventDispatcher();
            
            sequence = new SequenceRunner(this);
            
            dodgyPerson = create(Person, {'id': 1, 'firstname': 'John001', 'lastname': 'Smith001', 'phone': 6803225});
            soundPerson = create(Person, {'id': 2, 'firstname': 'John002', 'lastname': 'Smith002', 'phone': 6809168});
            
            service.add(dodgyPerson, soundPerson);
            
            service.dodgyPerson = dodgyPerson.id;
            service.soundPerson = soundPerson.id;
        }
From discussions I've had with my peers, it's the key insight and biggest stumbling block. Funnily enough, it's clearly documented here and here how to initialize a context programmatically, but I still needed help joinining the dots :-/

I borrowed those ideas and used them to implement a different approach:

  • One (potentially) monolithic FlexUnitApplicationContext can be broken down to contain only what's relevant to each test
  • The test method states which context to use, making it more clear/traceable what is in the test environment (think: Ctrl+click)
  • Different contexts allow for different service (interface) implementations i.e. less injection-by-id
  • Tools like FlashBuilder and Flexmojos are free to control (generate) the test application e.g. FlexUnitApplication

Both eventDispatcher and sequence are initialized - nothing exciting - and destroyContext() is also pretty straightforward.

Assignments to service.dodgyPerson, dodgyPerson service.soundPerson and soundPerson setup the agreement between the this test and the service.

Now let's review the rest of the code in method-pairs - this is is where the real magic happens...

Where the Real Magic Happens
First passItOn(...) and resultDoesNotManglePersonFound():
        [MessageHandler(selector='PersonEvent.found')]
        public function passItOn(event:PersonEvent):void {
            eventDispatcher.dispatchEvent(event);
        }
        
        [Test(async,description='Test GetPersonCommand result-handler does not mangle the person found.')]
        public function resultDoesNotManglePersonFound():void {
            dispatcher(PersonEvent.newLookup(soundPerson.id));
            
            sequence.addStep(new SequenceWaiter(eventDispatcher, PersonEvent.FOUND, 1000));
            
            sequence.addAssertHandler(function(event:PersonEvent, data:*):void {
                assertThat(event.type, equalTo('PersonEvent.found'));
                assertThat(event.person.fullname, equalTo('John002 Smith002'));
            });
            
            sequence.run();
        }
Looking first at the test-method, you can quite easily read the sequence of events:
  1. I send off a person lookup request
  2. I wait for the person to be found
  3. I check the person is who I expect
Looking into the detail, the lookup request is sent using the Parsley injected dispatcher i.e. using the Parsley Messaging Framework, yet the test is using Fluint (FlexUnit) to wait for the found notification i.e. using the plain-vanilla Flex Event Mechanism; the two technologies must be bridged - this is where passItOn(...) comes in.

passItOn(...) is adorned with the [MessageHandler] metadata tag, which Parsley will detect during initialization and wire-up to its Messaging Framework. Now, the test instance will be informed when a person is found. When that happens, this method simply uses the eventDispatcher to pass the found event onto the Flex Event Mechanism; where this eventDispatcher is the same one used by Fluint, the test execution can now proceed.

Now for rethrowIt(...) and resultVerifiesPersonFound():

        [MessageError(type='com.darrenbishop.crm.directory.application.PersonEvent')]
        public function rethrowIt(processor:MessageProcessor, error:Error):void { 
            eventDispatcher.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, error.message));
        }
        
        ...
        
        [Test(async,description='Test GetPersonCommand result-handler verifies the id of the person found.')]
        public function resultVerifiesPersonFound():void {
            dispatcher(PersonEvent.newLookup(dodgyPerson.id));
            
            sequence.addStep(new SequenceWaiter(eventDispatcher, ErrorEvent.ERROR, 1000));
            
            sequence.addAssertHandler(function(event:ErrorEvent, data:*):void {
                assertThat(event.text, equalTo(sprintf('Found person (%d) does not match lookup person (%d)', soundPerson.id, dodgyPerson.id)));
            });
            
            sequence.run();
        }
Again looking at the test-method first, you can see that:
  1. I send off a lookup request for the dodgy-person, thus triggering an error
  2. I wait for an error
  3. I check the error informs me of something remotely useful... or at least what the command thinks is useful
When I first tried to implement this negative test, it didn't work; naturally I had copy-pasted the unit-test and littered it with the async metadata-attribute and sequence stuff... no bueno.

But then it dawned on me that error detection i.e. for [Test(expect=...)], needed errors to occur in the same call-stack, so that when the stack unwinds, FlexUnit can use a try-catch block to detect and cross-check for the expect=... and make an assertion on the expected error type.

That's my unverified rationalization for why it didn't work, anyway, and for adopting a similar messaging approach as before, hence rethrowIt(...). I had previously thought the error was getting lost, but I knew there was a way to respond to them.

rethrowIt(...) is annotated with the [MessageError] metadata tag, which again, Parsley will detect during initialization and wire-up. So whenever there is an error in a [MessageHandler] or [CommandResult], Parsley will let the test know via rethrowIt(...).

Similar to passItOn(...), rethrowIt(...) uses the eventDispatcher to pass the error onto the Flex Event Mechanism; rethrowIt(...) is a little bit different in that it must marshal the Error object into a form the Flex Event Mechanism can understand, that is, an event and specifically a ErrorEvent instance.

What's Next...

Part 3: Hiding Fluint Sequences with a Flow-based DSL
I'll introduce the AsyncHelper base class and leverage FlexUnit's [Before] and [After] inheritance to abstract away all that Fluint Sequence stuff.

This approach makes available a bunch of helper methods that make asynchronous tests much easier to read.

Monday, April 11, 2011

Darren on Flex: FlexUnit4 Testing with Parsley - Unit Testing Command Objects

In Part I, I'll start off by implementing a 'purist' unit-test. In order to do this, however, I need a context; I think a noddy CRM system will do - I was tempted to use a Calculator system, but decided against it as there was too much temptation to actually implement some logic.
Ironically, the point of this blog series is not about testing logic :-/

CRM It Is Then...

As mentioned in the intro, I've been working with Cairngorm3 and typically testing Parsley Command objects and Cairngorm-style PresentationModels (PMs).

After applying the Cairngorm3 guidelines to the CRM system, I've structured the project as illustrated.

At the application-layer there is the:
  • DirectoryService interface, which will be stubbed and later used by Parsley to injection-by-type
  • GetPersonCommand, which in this exercise will be the unit-under-test (UUT)
  • and PersonEvent, which will eventually be used with Parsley's Messaging Framework to implement low-coupling.
In the domain-layer there's the lonely Person class; I'll be making assertions against this and at some point during the series I'll demonstrate two neat ways to build and leverage test-fixtures.

The 'main' Code...

First up is the DirectoryService interface
package com.darrenbishop.crm.directory.application {
    import mx.rpc.AsyncToken;
    
    public interface DirectoryService {
        function lookupById(id:uint):AsyncToken;
    }
}
Bit of a crappy CRM, with its one service call, but remember this is about testing the Command object, not service logic.
Next is the aforementioned UUT, the GetPersonCommand
This is a standard Command adhering to the execute-result-error method naming convention. These methods will be auto-detected by Parsley if the command is declared as a DynamicCommand; I'll use the [Metadata] tags when it comes to it:
package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.domain.Person;
    
    import mx.collections.ArrayCollection;
    import mx.rpc.AsyncToken;
    import mx.rpc.Fault;

    public class GetPersonCommand {
        public var dispatcher:Function;
        
        public var service:DirectoryService;
        
        public function execute(event:PersonEvent):AsyncToken {
            return service.lookupById(event.id);
        }
        
        public function result(person:Person, event:PersonEvent):void {
            if (person.id != event.id) {
                throw new Error(sprintf('Found person (%d) does not match lookup person (%d)', person.id, event.id));
            }
            dispatcher(PersonEvent.newFound(person));
        }
        
        public function error(f:Fault, event:PersonEvent):void {
        }
    }
}
You can see the execute method is a simple pass-through i.e. there is no outbound data-translation or encoding, etc. before the service call.

The result method doesn't do any inbound data-translation, etc. but I do introduce a bit of verification logic: where the event parameter is passed the original triggering event i.e. the same object received by the execute method, I have access to the id of the person requested; the person parameter is passed the person looked-up and found by the would-be CRM system's directory-service. I use this data to verify I have actually found the person I am looking for... put another way, I've contrived a situation where I can throw an error; you'll see I will force this situation to implement a negative-test.

Last but not least, the PersonEvent
Events are re-purposed in Parsley as messages in its Messaging Framework - broadly doing the same thing as the standard event-mechanism, but offering better de-coupling, implementing a topic-subscription approach with message selectors.
package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.domain.Person;
    
    import flash.events.Event;

    public class PersonEvent extends Event {
        public static const LOOKUP:String = 'PersonEvent.lookup';
        
        public static const FOUND:String = 'PersonEvent.found';
        
        private var _id:uint;
        
        public function get id():uint {
            return _id;
        }
        
        private var _person:Person;
        
        public function get person():Person {
            return _person;
        }
        
        function PersonEvent(_:Guard, type:String) {
            super(type);
        }
        
        public static function newLookup(id:uint):PersonEvent {
            var event:PersonEvent = new PersonEvent(Guard.IT, LOOKUP);
            event._id = id;
            return event;
        }

        public static function newFound(person:Person):PersonEvent {
            var event:PersonEvent = new PersonEvent(Guard.IT, FOUND);
            event._person = person;
            return event;
        }
        
        public override function clone():Event {
            var event:PersonEvent = new PersonEvent(Guard.IT, type);
            event._id = id;
            event._person = person;
            return event;
        }
    }
}

class Guard {
    internal static const IT:Guard = new Guard();
}
I use static factory methods here in-lieu of constructor-overloading - better anyway for removing confusion over what events are being constructed with what arguments. To restrict access to the sole constructor, I use a slight variation of the internal-Sentinel pattern that's quite common in Flex.

Anyway, that's it for the 'main' code, on to...

The 'test' Code...

I'll start with the actual test class
The FlexUnit4 GetPersonCommandUnitTest
This is a unit-test in the purist sense; all I'm interested in here is the behaviour of the command object, specifically, what goes on in the execute-result-error methods. I feed the command canned-data and spy on it's outputs by controlling the objects it collaborates with. In this test case, those would be the service and dispatcher objects; the latter, which is a Function object, I use directly to make assertions, the former I inspect and assert on what I find.
package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.domain.Person;
    import com.darrenbishop.support.create;
    
    import org.flexunit.assertThat;
    import org.flexunit.asserts.fail;
    import org.hamcrest.object.equalTo;
    
    public class GetPersonCommandUnitTest {
        public var service:StubLookupTrackingDirectoryService;
        public var command:GetPersonCommand;
        
        [Before]
        public function prepareCommand():void {
            service = new StubLookupTrackingDirectoryService();
            command = new GetPersonCommand();
            command.service = service;
        }
        
        [Test(description='Test GetPersonCommand calls the DirectoryService.lookupById(...) service correctly.')]
        public function lookUpByIdIsCalledCorrectly():void {
            service.add(create(Person, {'id': 001, 'firstname': 'John001', 'lastname': 'Smith001', 'phone': 6977150}));
            service.add(create(Person, {'id': 002, 'firstname': 'John002', 'lastname': 'Smith002', 'phone': 6809168}));
            service.add(create(Person, {'id': 003, 'firstname': 'John003', 'lastname': 'Smith003', 'phone': 7208442}));
            
            command.execute(PersonEvent.newLookup(002));
            command.execute(PersonEvent.newLookup(001));
            command.execute(PersonEvent.newLookup(003));
            
            assertThat(service.nextLookup().fullname, equalTo('John002 Smith002'));
            assertThat(service.nextLookup().fullname, equalTo('John001 Smith001'));
            assertThat(service.nextLookup().fullname, equalTo('John003 Smith003'));
        }
        
        [Test(description='Test GetPersonCommand result-handler does not mangle the person found.')]
        public function resultDoesNotManglePersonFound():void {
            command.dispatcher = function(event:PersonEvent):void {
                assertThat(event.person.fullname, equalTo('John002 Smith002'));
            };
            
            var person:Person = create(Person, {'id': 002, 'firstname': 'John002', 'lastname': 'Smith002', 'phone': 6809168});
            command.result(person, PersonEvent.newLookup(002));
        }
        
        [Test(expects='Error',description='Test GetPersonCommand result-handler verifies the id of the person found.')]
        public function resultVerifiesPersonFound():void {
            command.dispatcher = function(event:PersonEvent):void {  };
            
            var person:Person = create(Person, {'id': 001, 'firstname': 'John002', 'lastname': 'Smith002', 'phone': 6809168});
            command.result(person, PersonEvent.newLookup(002));
        }
    }
}
I've implemented three tests, including the one negative test, that gives 75% branch coverage; testing the error method is just the same as testing the result method, so I've skipped it.

Some might wonder why I don't use mocking to stub/mock the DirectoryService - I really ought to have but the integration options of, say mockito-flex (my favourite), are not available to me: I can't extend MockitoTestCase or use the MockitoClassRunner as in the next parts I use both mechanisms to implement the Parsley integration and DSL support.

On that note, here's the code for the stubbed DirectoryService...

Faking It: The StubLookupTrackingDirectoryService
package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.domain.Person;
    
    import flash.utils.Dictionary;
    
    import mx.rpc.AsyncToken;
    
    public class StubLookupTrackingDirectoryService implements DirectoryService {
        private var people:Dictionary;
        
        private var lookups:Array;
        
        public function StubLookupTrackingDirectoryService() {
            people = new Dictionary();
            resetLookups();
        }
        
        public function resetLookups():void {
            lookups = [];
        }
        
        public function add(person:Person):void {
            if (people[person.id]) {
                throw new Error('A person with id ' + person.id + ' already exists.');
            }
            
            people[person.id] = person;
        }
        
        public function nextLookup():Person {
            return lookups.shift();
        }
        
        public function lookupById(id:uint):AsyncToken {
            lookups.push(people[id]);
            return null;
        }
    }
}
You can see the stub has two variables:
  • people, a dictionary which keys Person objects against their id
  • lookups, which is used to track the order in which the Command object makes person lookups... I know, it screams out for mocking, right
The people dictionary was originally populated with a dozen or so Person objects; I refactored this as having the test fixture combined into the stub isn't so good. I pushed it out to the test methods, which is a better approach, providing self documenting tests; from reading the test method I know exactly: what reference-data I'm priming the system with i.e. the Person objects; what test-data I'm activating the system with i.e. the id to lookup with; what output-data I expect i.e. the found person. Much better.

What's Next...

Part 2: Asynchronous Testing with Parsley and Fluint Sequences
I'll use FlexUnit4's [Before] feature to build a Parsley context and dynamically initialize the test class with references to the object-under-test (OUT) and the OUT's dependencies.

I'll also show using Fluint Sequences to test a complete asynchronous flow through the Command object.

Tuesday, April 05, 2011

Darren on Flex: FlexUnit4 Testing with Parsley

I've explored in the past the features of FlexUnit4, specifically its asynchronous and UI capabilities, to test the behaviour of the mixins over at my Flexins Project. Recently, however, I've been using Cairngorm and Parsley on an assignment and I've channelled my efforts into testing UI-logic. I've deferred testing views for the time being, focusing on Parsley Command objects and Cairngorm-style presentation-models (PMs) - typically objects containing logic that is triggered by the Parsley Messaging framework.

I am using FlexUnit, but I recognise some might classify this testing as something other than unit-testing; calling it integration-testing seems a bit grand, seeing as for a Flex application, being traditionally client-server, I'm not integrating anything. I'd rather call it component-testing, looking at it from the point of view that I target the collaboration of a small subset of classes that support one functional area of the application.

Still, by saying that, what I really want to acknowledge is that there maybe better tools for this kind of testing. For example I am a fan of Behaviour Driven Development and have also explored using Cucumber, FunFX and Melomel.

This blog series is really just an account of what I have achieved in proving the concept of Parsley supporting FlexUnit testing.... of Cairngorm stuff.

Where I am a fan of DSLs, I will also show you how an embedded DSL can be used to introduce a bit of flow to your tests and abstract away details that gradually starts to make my head spin.

I re-iterate, it's all just a display of what can be done - it's by no means polished and I'm not even claiming that it's a good approach - it's simply a workable approach to shake-down your message wiring and interactions between Parsley-managed objects.

  1. Unit Testing Command Objects
  2. Asynchronous Testing with Parsley and Fluint Sequences
  3. Hiding Fluint Sequences with a Flow-based DSL
  4. Improved Parsley Support with FlexUnit's [RunWith(...)][Rule]
  5. Testing with a Parsley-Aware DSL
  6. Testing with Mock Injections
I hope you find it useful.

Update:

FlexUnit 4.1 has been released, providing new features including the handy [Rule] hook-mechanism