Mock Webservice in AS3

An important part of a good test suite is repeatability, and independance from external changes. Therefore, if your method loads data from an external source, that’s a real testing no-no.

However, using the features of dynamic classes in AS3 we can whip up a mock webservice which gives us a guaranteed result, every time.

Here’s the class:

package com.martypitt.testing.mockObjects
{
    import flash.utils.Dictionary;
    import flash.utils.flash_proxy;

    import mx.core.Application;
    import mx.core.mx_internal;
    import mx.messaging.messages.AsyncMessage;
    import mx.messaging.messages.IMessage;
    import mx.rpc.AbstractOperation;
    import mx.rpc.AsyncToken;
    import mx.rpc.events.ResultEvent;
    import mx.rpc.soap.WebService;

    use namespace flash_proxy;
    use namespace mx_internal;

    public dynamic class MockWebService extends WebService
    {

        /** The default response is returned if a call is made to a method that has not been explicitly
         * set using setResponse() */
        private var _defaultResult:Object;

        /** Determines bevahiour if a call is made to a method where the response has not been explicity set.
         * If true, an error is thrown.
         * If false (default) the defaultResponse is returned.
         * */
        private var _throwErrorIfResultNotSet:Boolean = false;

        private var _resultDictionary:Dictionary = new Dictionary();

        public function get defaultResult():Object {
            return _defaultResult;
        }
        public function set defaultResult(value:Object):void {
            _defaultResult = value;
        }

        /** responseDictionary holds a list of predefined methods and their associated responses.
         * */
        protected function get resultDictionary():Dictionary {
            return _resultDictionary;
        }
        protected function set resultDictionary(value:Dictionary):void {
            _resultDictionary = value;
        }
        public function get throwErrorIfResultNotSet():Boolean {
            return _throwErrorIfResultNotSet;
        }
        public function set throwErrorIfResultNotSet(value:Boolean):void {
            _throwErrorIfResultNotSet = value;
        }

        public function MockWebService(destination:String=null, rootURL:String=null)
        {
            super(destination, rootURL);
        }

        /** callProperty is called by the Flex / flash framework when something tries to access
         * a method or property of a dynamic object which has not been defined in code.
         * Here we override the default behaviour to return the correct response.
         * */
        override flash_proxy function callProperty(name:*, ... args:Array):*
        {
            var response:Object;
            var methodName:String = getLocalName(name);

            switch (true) {
                case (_resultDictionary[methodName] == null && throwErrorIfResultNotSet) :
                    throw new Error(String(methodName) + " does not have an appropriate result set.");

                case (_resultDictionary[methodName] == null && !throwErrorIfResultNotSet) :
                    response = defaultResult;
                    break;

                case (_resultDictionary[methodName] != null) :
                    response = _resultDictionary[methodName]
                    break;

                default :
                    throw new Error("Unhandled switch case scenario in MockWebService");
            }

            var message:IMessage = new AsyncMessage()
            var token:AsyncToken = new AsyncToken(message)

            // We want to return the Async token now (just like a normal webservice would)
            // but we get the app to dispatch the ResultEvent on the next frame

            // It's important that the result event is dispatched from the operation itself.
            var operation:AbstractOperation = super.getOperation(methodName);
            Application.application.callLater(operation.dispatchEvent,[ResultEvent.createEvent(response,token,message)]);
            return token;
        }

        public function setResult(methodName:String,responseValue:Object):void {
            _resultDictionary[methodName] = responseValue;
        }

    }
}

Nothing overly swanky, but all the magic happens courtesy of this line :
override flash_proxy function callProperty(name:*, ... args:Array):*

This method gets invoked whenever you call a method on the class which has not been defined in code. We catch the call, and generate an Async token that makes the caller think that we’re busily off chatting to our webserver.

The other key line is this:
Application.application.callLater(operation.dispatchEvent,[ResultEvent.createEvent(response,token,message)]);

In essence, we’re just dispatching a result event here. But, because our method is called syncronously, and webservices behave asyncronously, it’s important that we dispatch the event on the next frame. Hence Application.application.callLater.

Also, the result event must come from the operation itself, not our mock service, so that whoever has called this method gets notified of the result event, just like they would if it were a normal webservice.

So, with all that done, we simply set up the webservice, and invoke it:


var result:XML = new XML();
var ws:MockWebService = new MockWebService();
ws.setResult("callMyRemoteMethod",result)
someClass.webservice = ws;

It’s important (and good design practice) that you expose the webservice on your class as property that can be modified at runtime.

That’s pretty much it.

Questions and comments welcomed.

Marty

Advertisements
Tagged , ,

2 thoughts on “Mock Webservice in AS3

  1. pascualin says:

    Do you know how to make this work when running Flex UnitTests??

    It seems like dispatching the event in Flex 4 doesn’t get picked up by the resultHandler:

    FlexGlobals.topLevelApplication.callLater(operation.dispatchEvent, [ResultEvent.createEvent(response, token, message)]);

  2. pascualin says:

    I forgot to tick the “notify me of follow-up comments” 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: