Showing posts with label Testing. Show all posts
Showing posts with label Testing. Show all posts

Wednesday, November 19, 2014

Darren on Flex: FlexUnit4 Testing with Parsley - Testing with Mock Injections

In Part-5 I showed how to tidy away all (most of) the Parsley Messaging and Flex event management boiler-plate by extending the Fluint-aware DSL introduced in Part-3, which made tests Parsley-aware.

This much overdue final part of the FlexUnit4 Testing with Parsley series demonstrates mock injection and leverages Mockito-Flex to achieve that.
(I will give a shout to the team behind Mockolate, as that is also a very good mocking library)

Mockito-Flex utilizes ASMock internally and I encountered a problem where the application-domain used by ASMock for creating mocks, differed from that used by Parsley for its managed context environment.
I managed to clone and patch the Mockito-Flex codebase (pull-request imminent) such that in this patched version, Mockito-Flex specifies to ASMock what application domain to use. Awesome!

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

Mock Injection
Previously, a stubbed service implementation was used to provide the behaviour needed for the successful running of the tests.
The smell that comes with using stubs is that they tend to introduce test logic, as was the case here, which is:
  1. Only implicitly tested, by default of being used in a test and...
  2. Only ever used through test code and not main/business code, so does not really represent any business value
Furthermore, this test logic resides in a different class to the test making it harder to understand what is going on (or harder to debug it if/when something goes wrong).

We can rid ourselves of these code-smells by using mocking; the DSL provided by Mockito-Flex allows us to declaritively specify how the mocks in our Parsley context must behave AND we do that in-line with our tests.

With the mocking and Parsley-aware DSLs combined, we still get to write easy-to-read story-style tests i.e. given-when-then, like we did before.

DSL(Mocking + Parsley) - Inheritance = A New Approach
package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.GetPersonCommandMockedComponentTestContext;
    import com.darrenbishop.crm.directory.domain.Person;
    import com.darrenbishop.support.create;
    import com.darrenbishop.support.flexunit.parsley.ParsleyDslRule;
    import com.darrenbishop.support.flexunit.parsley.dsl.*;

    import flash.events.ErrorEvent;

    import org.hamcrest.assertThat;
    import org.hamcrest.object.equalTo;
    import org.mockito.integrations.flexunit4.MockitoRule;
    import org.mockito.integrations.given;

    public class GetPersonCommandMockedParsleyRuleDSLComponentTest {

        [Rule(order=1)]
        public var mockito:MockitoRule = new MockitoRule();
        
        [Rule(order=2)]
        public var parsley:ParsleyDslRule = new ParsleyDslRule(GetPersonCommandMockedComponentTestContext);
        
        [Mock]
        public var service:DirectoryService;
        
        [Inject]
        public var command:GetPersonCommand;
        
        public var dodgyPerson:Person;
        
        public var soundPerson:Person;
        
        [Before]
        public function prepareEntities():void {
            dodgyPerson = create(Person, {'id': 1, 'firstname': 'John001', 'lastname': 'Smith001', 'phone': 6803225});
            soundPerson = create(Person, {'id': 2, 'firstname': 'John002', 'lastname': 'Smith002', 'phone': 6809168});
        }
        
        [Test(async,description='Test GetPersonCommand result-handler does not mangle the person found.')]
        public function resultDoesNotManglePersonFound():void {
            given(service.lookupById(soundPerson.id)).will(returnEventually(soundPerson));

            dispatchMessage(PersonEvent.newLookup(soundPerson.id));
            
            waitForMessage(PersonEvent.FOUND);
            then(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 {
            given(service.lookupById(dodgyPerson.id)).will(returnEventually(soundPerson));

            dispatchMessage(PersonEvent.newLookup(dodgyPerson.id));
            
            waitForError(ErrorEvent.ERROR);
            then(function(event:ErrorEvent, data:*):void {
                assertThat(event.text, equalTo(fmt('Found person (%d) does not match lookup person (%d)', soundPerson.id, dodgyPerson.id)));
            });
        }
    }
}

The first thing to notice is the test-case no-longer needs to inherit from the ParsleyHelper class; all that DSL goodness is made available in standalone functions, in much the same way as those provided in the org.hamcrest.*, org.mockito.integrations.* packages and Flex' top-level package.

Swapping out ParsleyRule and using ParsleyDslRule instead activates this non-inheritance approach; under-the-hood it ensures that each test will run in isolation and that any invoked Parsley-aware DSL-function will use the same singletons/managed-objects for that test e.g. the same [MessageDispatcher] or EventDispatcher.

Some test developers use inheritance to implement a suite of similar tests - given Flex only supports single-inheritance - it is nice to have the inheritance-chain freed-up to allow them to continue to do so.

That's all there really is to it!

The change to Mockito-Flex that allows us to specify the application domain is included below, along with the two additional classes that are needed to make the mock-injection work:

--- mockito-flex/mockito/src/main/flex/org/mockito/integrations/flexunit4/MockitoRule.as (revision 87:8a8720f988e55bc8daf60144d8d73e63a8cc69b7)
+++ mockito-flex/mockito/src/main/flex/org/mockito/integrations/flexunit4/MockitoRule.as (revision 87+:8a8720f988e5+)
@@ -10,6 +10,7 @@
 import org.flexunit.runners.model.FrameworkMethod;
 import org.flexunit.token.AsyncTestToken;
 import org.mockito.Mockito;
+import org.mockito.impl.AsmockADMockeryProvider;
 import org.mockito.integrations.currentMockito;
 
 public class MockitoRule extends MethodRuleBase implements IMethodRule
@@ -19,7 +20,7 @@
         super();
     }
 
-    private function classFor(test:Object):Class
+    protected function classFor(test:Object):Class
     {
         return Class(getDefinitionByName(getQualifiedClassName(test)));
     }
@@ -27,7 +28,7 @@
     override public function apply(base:IAsyncStatement, method:FrameworkMethod, test:Object):IAsyncStatement
     {
         var sequencer:StatementSequencer = new StatementSequencer();
-        currentMockito = new Mockito();
+        currentMockito = new Mockito(AsmockADMockeryProvider);
         sequencer.addStep(withMocksPreparation(classFor(test)));
         sequencer.addStep(withMocksAssignment(classFor(test), test));
         sequencer.addStep(base);

package org.mockito.impl
{
    import org.mockito.api.MockCreator;
    import org.mockito.api.MockInterceptor;
    import org.mockito.api.MockeryProvider;
    import org.mockito.api.SequenceNumberGenerator;
    import org.mockito.api.SequenceNumberTracker;

    /**
     *
     * Implementation of the mockery provider that creates asmock based implementation
     */
    public class AsmockADMockeryProvider implements MockeryProvider
    {
        private var mockInterceptor:MockInterceptor;

        private var mockCreator:AsmockADMockery;

        /**
         * Creates mockery provider
         */
        public function AsmockADMockeryProvider(sequenceNumberGenerator:SequenceNumberGenerator, sequenceNumberTracker:SequenceNumberTracker)
        {
            mockInterceptor = new MockInterceptorImpl(sequenceNumberTracker);
            mockCreator = new AsmockADMockery(mockInterceptor, sequenceNumberGenerator);
        }

        /**
         * Returns MockCreator
         * @return implementation of MockCreator
         */
        public function getMockCreator():MockCreator
        {
            return mockCreator;
        }

        /**
         * Returns MockInterceptor
         * @return implementation of MockInterceptor
         *
         */
        public function getMockInterceptor():MockInterceptor
        {
            return mockInterceptor;
        }
    }
}

package org.mockito.impl
{
    import asmock.framework.MockRepository;
    import asmock.framework.asmock_internal;

    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.IEventDispatcher;
    import flash.system.ApplicationDomain;
    import flash.utils.Dictionary;

    import org.flemit.reflection.MethodInfo;
    import org.flemit.reflection.Type;
    import org.floxy.IInvocation;
    import org.mockito.api.Invocation;
    import org.mockito.api.MockCreator;
    import org.mockito.api.MockInterceptor;
    import org.mockito.api.SequenceNumberGenerator;

    use namespace asmock_internal;

    /**
     * Asmock bridge. Utilizes asmock facilities to create mock objects.
     * @private
     */
    public class AsmockADMockery extends MockRepository implements MockCreator
    {
        public var interceptor:MockInterceptor;

        protected var _names:Dictionary = new Dictionary();

        private var sequenceNumberGenerator:SequenceNumberGenerator;

        public function AsmockADMockery(interceptor:MockInterceptor, sequenceNumberGenerator:SequenceNumberGenerator)
        {
            super(new ADProxyRepository(ApplicationDomain.currentDomain));
            this.interceptor = interceptor;
            this.sequenceNumberGenerator = sequenceNumberGenerator;
        }

        /**
         * A factory method that creates Invocation out of asmock invocation
         * @param invocation asmock invocation object
         * @return mockito invocation
         *
         */
        public function createFrom(invocation:IInvocation):Invocation
        {
            var niceMethodName:String = new AsmockMethodNameFormatter().extractFunctionName(invocation.method.fullName);
            return new InvocationImpl(invocation.invocationTarget,
                    invocation.method.fullName,
                    invocation.arguments,
                    new AsmockOriginalCallSeam(invocation),
                    sequenceNumberGenerator.next());
        }

        public function prepareClasses(classes:Array, calledWhenClassesReady:Function, calledWhenPreparingClassesFailed:Function = null):void
        {
            var dispatcher:IEventDispatcher = super.prepare(classes);
            var repositoryPreparedHandler:Function = function (e:Event):void
            {
                calledWhenClassesReady();
            };
            
            var repositoryPreparationFailed:Function = function (e:Event):void
            {
                if (calledWhenPreparingClassesFailed != null)
                    calledWhenPreparingClassesFailed();
            };
            dispatcher.addEventListener(Event.COMPLETE, repositoryPreparedHandler);
            dispatcher.addEventListener(ErrorEvent.ERROR, repositoryPreparationFailed);
        }

        public function mock(clazz:Class, name:String = null, constructorArgs:Array = null):*
        {
            if (name == null)
            {
                name = Type.getType(clazz).name;
            }
            var mock:Object = createStrict(clazz, constructorArgs);
            registerAlias(mock, name);
            return mock;
        }

        override asmock_internal function methodCall(invocation:IInvocation, target:Object, method:MethodInfo, arguments:Array):*
        {
            return interceptor.methodCalled(createFrom(invocation));
        }

        /**
         *
         * @param mock
         * @param name
         */
        public function registerAlias(mock:Object, name:String):void
        {
            _names[mock] = name;
        }
    }
}

Epilogue


Although I doubt anyone has been waiting, apologies for taking so long to wrap this all up.
All the same, it was an interesting exercise to figure all this out.

Ironically, I likely will not be engaging in any Flex development going forward, as I have recently turned my attention to iOS and SCALA.
However I will commit to getting the Mockito-Flex changes pushed to my fork on BitBucket and raise a pull request with Loomis, the author. DONE!

Thanks for reading and comments are welcome as always.

Thursday, June 16, 2011

Darren on Flex: FlexUnit4 Testing with Parsley - Testing with a Parsley-Aware DSL

In Part-4 I showed how to use [RunWith] and [Rule] to locate and hide Parsley context initialization; with the reduced duplication in this approach tests are left to focus on what is important - testing.

In Part-5, I'll show how to reduce the noise hide the complexities of Parsley Messaging, used to implement decoupling between components, by extending the Fluint-aware DSL introduced in Part-3 to be Parsley-aware.

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

Hiding Messages
As mentioned, this improved DSL attempts to hide uses of the Parsley Framework. The approach is limited to hiding usage of the Parsley Messaging API, but supports a comfortable balance of declarative and imperative coding seen in story-style testing i.e. given-when-then.

We retain the declarative approach to dependency injection i.e. with [Inject], yet this approach is less fitting for messaging/coupling management in a test(-method). Story tests tend to read as a sequence of imperative commands to prepare (Given), actuate (When) and inspect (Then) the object-under-test. In this style you often specify which message to dispatch and which to listen out for... and when... and for how long (timeout). You have the opportunity to state your messaging intentions clearly, in-line, that is, not having a test distributed over several methods (some of which being annotated with [MessageHandler(...)] metadata tags).

package com.darrenbishop.crm.directory.application {
    import com.darrenbishop.crm.directory.GetPersonCommandComponentTestContext;
    import com.darrenbishop.support.flexunit.parsley.ParsleyHelper;
    
    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.parsley.ParsleyRule;
    
    public class GetPersonCommandParsleyRuleDSLComponentTest extends ParsleyHelper {
        [Rule]
        public var rule:ParsleyRule = new ParsleyRule(GetPersonCommandComponentTestContext);
        
        [Inject]
        public var service:StubDirectoryService;
        
        [Inject]
        public var command:GetPersonCommand;
        
        public var dodgyPerson:Person;
        
        public var soundPerson:Person;
        
        [Before]
        public function primeService():void {
            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;
        }
        
        [Test(async,description='Test GetPersonCommand result-handler does not mangle the person found.')]
        public function resultDoesNotManglePersonFound():void {
            dispatchMessage(PersonEvent.newLookup(soundPerson.id));
            waitForMessage(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 {
            dispatchMessage(PersonEvent.newLookup(dodgyPerson.id));
            waitForMessage(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 the test class now extends ParsleyHelper, building on the inheritance approach to provide access to the messaging-DSL methods; you'll see shortly that this new helper class extends from AsyncHelper, so the async-DSL methods are still available.

There are a number of changes to note and to make them clear I've provided a diff generated from the test above and that presented in Part-4:

--- C:/Dev/workspaces/flex/parsley-flexunit/src/test/flex/com/darrenbishop/crm/directory/application/GetPersonCommandParsleyRuleComponentTest.as Wed Jun 15 21:00:39 2011
+++ C:/Dev/workspaces/flex/parsley-flexunit/src/test/flex/com/darrenbishop/crm/directory/application/GetPersonCommandParsleyRuleDSLComponentTest.as Wed Jun 15 20:54:17 2011
@@ -4,3 +4,3 @@
     import com.darrenbishop.support.create;
-    import com.darrenbishop.support.flexunit.async.AsyncHelper;
+    import com.darrenbishop.support.flexunit.parsley.ParsleyHelper;
     import com.darrenbishop.support.flexunit.parsley.ParsleyRule;
@@ -8,3 +8,2 @@
     import flash.events.ErrorEvent;
-    import flash.events.EventDispatcher;
     
@@ -12,5 +11,4 @@
     import org.hamcrest.object.equalTo;
-    import org.spicefactory.parsley.core.messaging.MessageProcessor;
     
-    public class GetPersonCommandParsleyRuleComponentTest extends AsyncHelper {
+    public class GetPersonCommandParsleyRuleDSLComponentTest extends ParsleyHelper {
         [Rule]
@@ -18,5 +16,2 @@
         
-        [MessageDispatcher]
-        public var dispatcher:Function;
-        
         [Inject]
@@ -31,8 +26,4 @@
         
-        private var eventDispatcher:EventDispatcher;
-        
         [Before]
         public function primeService():void {
-            eventDispatcher = new EventDispatcher();
-            
             dodgyPerson = create(Person, {'id': 1, 'firstname': 'John001', 'lastname': 'Smith001', 'phone': 6803225});
@@ -46,16 +37,6 @@
         
-        [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);
+            dispatchMessage(PersonEvent.newLookup(soundPerson.id));
+            waitForMessage(PersonEvent.FOUND, 1000);
             thenAssert(function(event:PersonEvent, data:*):void {
@@ -68,4 +49,4 @@
         public function resultVerifiesPersonFound():void {
-            dispatcher(PersonEvent.newLookup(dodgyPerson.id));
-            waitFor(eventDispatcher, ErrorEvent.ERROR, 1000);
+            dispatchMessage(PersonEvent.newLookup(dodgyPerson.id));
+            waitForMessage(ErrorEvent.ERROR, 1000);
             thenAssert(function(event:ErrorEvent, data:*):void {
Counting the new lines (6) and the dropped lines (20, not including blank-lines) reveals a net 14 line reduction of boilerplate code that would otherwise be repeated as your tests grow in quantity and size.

With this improved DSL, tests become even simpler to read.

[MessageDispatcher] - refactored
As mentioned all the explicit Parsley Messaging Framework usage has been pulled up to the ParsleyHelper super class:
package com.darrenbishop.support.flexunit.parsley {
    import com.darrenbishop.support.flexunit.async.AsyncHelper;
    
    import flash.events.Event;
    import flash.events.EventDispatcher;
    
    import org.spicefactory.parsley.core.messaging.MessageProcessor;

    public class ParsleyHelper extends AsyncHelper {
        [MessageDispatcher]
        public var dispatcher:Function;
        
        protected var eventDispatcher:EventDispatcher;
        
        [Before]
        public function prepareEventDispatcher():void {
            eventDispatcher = new EventDispatcher();
        }
        
        [MessageError]
        public function rethrowIt(processor:MessageProcessor, error:Error):void { 
            eventDispatcher.dispatchEvent(toEvent(error));
        }
        
        [MessageHandler]
        public function passItOn(msg:Event):Boolean {
            return eventDispatcher.dispatchEvent(msg);
        }
        
        protected function dispatchEvent(event:Event):void {
            eventDispatcher.dispatchEvent(event);
        }
        
        protected function dispatchMessage(event:Event):void {
            dispatcher(event);
        }
        
        protected function dispatchError(error:Error):void {
            eventDispatcher.dispatchEvent(toEvent(error));
        }
        
        protected function waitForMessage(eventType:String, timeout:Number=1000):void {
            waitFor(eventDispatcher, eventType, timeout);
        }
        
        protected function waitForError(eventType:String, timeout:Number=1000):void {
            waitForMessage(eventType, timeout);
        }
    }
}
Here we have all the methods pertinent to Parsley messaging, laid out and self explanatory.

Furthermore this approach does a good job of hiding usage of the Flex Event mechanism; this messaging-DSL removes the need to interact explicitly with an EventDispatcher.

What's Next...

Part 6: All That, But With Mocking
So I've got one more Ace up my sleeve - mock-injection. At this point I'm not even sure this is a good idea or whether there's a place for this technique in any practical testing scenario. I have recently read the Spring documentation on this and have since seen this done in enough places to be convinced there is value in it for component/sub-system testing.

Nonetheless, I figured it out and implemented it; the implementation in fact requires some modifications, including some to Mockito-Flex classes. I've contributed these code changes to the Mockito-Flex project, which I believe will be rolled in or otherwise accommodated soon; you can read more about it here

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

Sunday, September 21, 2008

Darren on Flex: Overloading the Constructor

Prologue

Back on the 3rd September 2008 some of my colleagues at Zuhlke unvailed StuffPlanner at a BCS SPA evening talk. StuffPlanner is an Agile estimation and planning tool with an opinion. Recently, I found myself ramping up my involvement in the development of StuffPlanner. Consequently, I have resumed my study and assessment of the technologies involved and their corresponding conventions and recommended usage. Henceforth, I blog briefly on the topic of constructor overloading.

Wait! ActionScript Does Not Support Overloading!

No, it doesn't... but please, keep reading. Sometime this week I received a communicade from a local colleague who is doing some pretty interesting work on the continent on the Flex platform. He asked me how to, or rather can you, overload a constructor. We (the locals) had (less) recently had a debate about this after realising that AS3 (ActionScript 3) does not support overloading, so I was primed to make a suitable response.

Default Parameters

Basically, in lieu of overloading AS3 gives you default parameters
public function DefaultValueExample(required:String, andTheCommonCase:String="DEFAULT") {
    if (!required) {
        throw new ArgumentError("You must specify required arguments");
    }

    // construct something with the required value andTheCommonCase
} 
Obviously the API designer needs to know the complete set of parameters, that is, those that must be specified and those that can have a default values. So far my references to the common-case are just to the concept of it, a place holder. The common-case represents the design challenge in using this language feature while, simultaneously, it represents the design limitation; the common-case(s) is prescribed to all the API clients irrespective of client needs. However, in practice the common-case is not a difficult trade-off to figure out and tends to be only a minor inconvenience. For example, in my experience, such cases tend to crop up during unit testing, which I anticipate will be less of a problem with TDD (yes, I am still homing my skills with TDD on Flex/AS3).

Variable Arguments

It may also be useful to use varargs to replace constructor overloading.
public function VarArgExample(... args) {
    for each (var arg:* in args) {
        // process arg...
    }
    //... then do construction
}
I reckon this has less application than default parameters as it only makes-sense/works when the (signature of the) things you are using during construction have parameters of no-type (* or Object) or a common super-type. Then you can either leverage duck-typing or polymorphism, respectively, to do smart things, which I won't discuss here.

The Factory Pattern

Oh yes, that old gem! I am a growing fan of using intention revealing names for variables and methods. It is perhaps a more common/old practice for variables (although you'd be surprised, even today), where variables are named to reveal the state it represents and/or for what it could be used to achieve. With methods, on the other hand, I sense a stigma, a taboo, in using this technique in any progressive way. Thus I feel there is a general hesitance to use this practice, as if it would imply some mediocrity in the coder's skill at writing good code. I do feel TDD development has gone some way to alleviate this, specifically, where test method names describe the example situation being tested; this some how seems more socially acceptable. And, while I'm at it, I'd like to suggest that this technique encourages you to write more cohesive methods w.r.t. what an object's methods do to contribute to the object's overall task. The reason I discuss this here (I did not plan to get into it that much) is because AS3 does not allow you to reuse method names i.e. no overloading. So rather than settling for less, why not combine intention revealing names with the Factory pattern. Now I have said it, I bet you are realizing you do this all the time (I anticipate) during testing, where you might have refactored some creational code for some objects used commonly in your tests. So what if we drop the usual XxxFactory's getInstance(), newInstances() and createInstance() cruft and adopt something more revealing?
//Person.as
public class Person {
    public function Person(firstName:String="", lastName:String="", ... middleNames) {
        // do somthing
    }  
}

//PersonFactory.as
public class PersonFactory {
    public function newPersonWithFirstName(firstname:String):Person { return new Person(firstname); }
    public function newPersonWithLastName(lastname:String):Person { return new Person(null, lastname); } // A little inconvenient
    public function newPersonWithFirstAndLastName(firstname:String, lastname:String):Person { return new Person(firstname, lastname); }
}
So what's the runtime overhead? Just the extra PersonFactory class, where the would-be constructor overloads are replaced with simple methods; let's face it, negligible. And the API overhead? Well interestingly, a problem has emerged and a pattern has been used to solve it. Furthermore, our intention revealing names should improve readability of the code and identifies a good place to refactor/relocate creational code that gets used over and over again... like, for instance, in your tests. Comments welcome.

Saturday, September 20, 2008

Darren on Flex: New FlexUnit 0.9 Out!

FlexUnit-0.9 has been released by Adobe Cosulting. I believe this is the first release since the projects move from Google Code to Adobe open Source. I have browsed the source code and nothing seems to have changed; obviously, community, please can correct me if I am wrong. There is still a lingering reference to the flexunit.extensions.TestSetup class which is still nowhere to be seen. Indeed I discovered the update while trying to find the standard approach to test-suite setup and teardown; subquently deciding it is is not the solution to my problem anyway. The most significant change, if only change, is the UI. We have:
  • An active search filter that narrows down the displayed list of test methods, matching on test (method) name.
  • A combobox with three result options: All Results, Empty Tests and Failures & errors; this replaces the tabbed view in the old version.
  • A red cross or green tick indicating the obvious, with a fat red cross or green tick to indicate overall (test-suite) pass or failure; it's a sad day - we've lost our beloved red-bar-green-bar.
  • In the event of an error/failure, improved diagnostic feedback. Just the other day I was getting frustrated with the old version, scrolling through the stack trace to find what class and which line of code I wrote (as opposed to FlexUnit code) that caused the breakage; well, it seems I won't have to suffer that anymore.
Overall, it just looks a lot more charming. Check it out, literally.