Using Stubs for HTTPService and RemoteObject in Flex

Brian LeGros | February 21st, 2009 | programming  

UPDATE: I’ve finally created a project for these stubs on GitHub @ http://github.com/blegros/flexRpcStubs. Please consider this code more up to date then what follows below.

Recently I’ve been working on producing stub versions of HTTPService and RemoteObject for some integration tests I’m writing. If you haven’t worked with the concept of a stub before, think of a stub a re-implementation of an object created to produced canned answers to calls made to it during a test (borrowed from Mocks aren’t Stubs). A stub is intended to be purposefully ignorant to everything but what is being asked of it in a test. If you’re familiar with the concept of a mock object, a stub can be considered a specialization of a mock object that has no expectations to manage rather only return values. In a lot of circles the term mock and stub are used interchangeably, but I think the difference is important to note. If you’re interested more in the subject, come and see my presentation at FlexCamp Miami on March 6th, 2009.

Typically, in the case of integration testing, stubs are used to impersonate objects which have direct contact with resources external to your application, or component. In the context of most programming langauges I’ve worked with, these are typically classes built into extension libraries for the language (e.g. – JDBC, log4j, javax.mail, etc). In the context of Flex, services are the primary sets of classes that we end up wishing we could stub out. Unfortunately, the need to stub these types of classes can introduce some complexities, especially without a good mock object framework, because very rarely are the interfaces to these classes simple enough for a developer to impersonate on the fly (i.e. – inline in a single test). In the Java world, frameworks like Spring will create helper objects for testing to supplement these needs (e.g. – MockHttpSession and AbstractTransactionalJUnit4SpringContextTests).

In Flex, I haven’t really found a great set implementations for stubs yet, so this weekend I threw together a first draft of working stub classes for HTTPService and RemoteObject in AS3 using the Flex SDK 3.2. I’ve been using variants of these for my testing and so far, so good. Anyone interested in stubbing WebService, can proabably create a similar model to what I’ve done in RemoteObjectStub and be successful. Each stub is type-safe and adds the following properties for configuration to their base classes:

  • delay : Number – Default: 1000 – Number of milliseconds to delay calling registered responders and dispatch the appropriate events for the stub.
  • result : Function – The method used to map result, or fault, objects to specific signatures of send (for HTTPService) or a method name (for RemoteObject). If passed an object deriving from type mx.rpc.Fault, a mx.rpc.events.FaultEvent will be dispatched rather than a mx.rpc.events.ResultEvent.
  • fault : Function – A sugar method that will take in a faultCode, faultString, and faultDetail to create a mx.rpc.Fault and delegate to result().

Each class also supports the dispatching of the appropriate events to mx.rpc.AsyncToken objects as well as the stub instances themselves, just like their base classes. Here is a example of using HTTPServiceStub, in place of HTTPService, in a test case written using fluint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ClassWithHTTPServiceDependencyTest extends TestCase
{
   private var _classBeingTested : ClassWithHTTPServiceDependency;
   private var _stub : HTTPServiceStub;
 
   override protected function setUp() : void
   {
      _classBeingTested = new ClassWithHTTPServiceDependency();
      _stub = new HTTPServiceStub("http://thisisntarealdomain.com");
      _stub.delay = 500;
      _classBeingTested.service = _stub;
   }
 
   public function testSendWithTokenResult() : void
   {
      var result : Function = function (event : DynamicEvent, passThroughData : Object) : void
      {
         assertEquals("GOAL!", event.payload);
      };
 
      _stub.result(null, "GOAL!");
 
      _classBeingTested.addEventListener("success", asyncHandler(result, 2000));
      _classBeingTested.someMethodUsingHTTPService();
   }
}

In the above example, the behavior being tested is that when a ResultEvent is dispatched to ClassWithHTTPServiceDependency, it dispatches its own mx.events.DynamicEvent with a reference to the result property of the ResultEvent in its payload property. Please note, the call to send() is being made inside of someMethodUsingHTTPService() but could really be made anywhere inside of the object, we don’t care since the stub can impersonate HTTPService.

The implementations for the HTTPServiceStub and RemoteObjectStub are something I hope to get feedback on and maybe get included in fluint or mock-as3, although I’m sure Drew can think of sexier implementations. Below is the source for anyone who is interested in giving them a go to see if they can help your testing process. I have a few unit tests for each as well, so if anyone’s interested, just let me know and I’ll post that code as well.

Happy coding and here’s to these not working and failing you miserably …

HTTPServiceStub.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package net.digitalprimates.fluint.stubs
{
   import flash.events.TimerEvent;
   import flash.utils.Dictionary;
   import flash.utils.Timer;
 
   import mx.rpc.AsyncToken;
   import mx.rpc.Fault;
   import mx.rpc.IResponder;
   import mx.rpc.events.AbstractEvent;
   import mx.rpc.events.FaultEvent;
   import mx.rpc.events.ResultEvent;
   import mx.rpc.http.HTTPService;
 
   /**
    * Idea borrowed from Sonke Rohde @ http://soenkerohde.com/2008/10/conditional-compilation-to-mock-with-swiz/
    **/
   public class HTTPServiceStub extends HTTPService
   {
      private var _resultData : Dictionary;
 
      //default num of milliseconds to wait before dispatching events
      //don't put too low otherwise your token responders may not be registered
      public var delay : Number = 1000;
 
      private var token : AsyncToken;
      private var parameters : Object;
 
      public function HTTPServiceStub(rootURL : String = null, destination : String = null)
      {
         super(rootURL, destination);
         _resultData = new Dictionary();
      }
 
      public function result(parameters : Object, data : *) : void
      {
         _resultData[parameters] = data;
      }
 
      public function fault(parameters : Object, code : String, string : String, detail : String) : void
      {
         var fault : Fault = new Fault(code, string, detail);
         this.result(parameters, fault);
      }
 
      override public function send(parameters : Object = null) : AsyncToken
      {
         return configureResponseTimer(parameters);
      }
 
      private function configureResponseTimer(parameters : Object) : AsyncToken
      {
         token = new AsyncToken(null);
         this.parameters = parameters;
 
         //use a time to give time for the caller to map responders to the asyncToken
         var timer : Timer = new Timer(this.delay, 1);
         timer.addEventListener(TimerEvent.TIMER_COMPLETE, handleTimer);
 
         timer.start();
 
         return token;
      }
 
      private function handleTimer(event : TimerEvent) : void
      {
         //clean-up
         event.target.removeEventListener(TimerEvent.TIMER_COMPLETE, handleTimer);
 
         //loop over all responders to emulate a successful call being made
         for each(var responder : IResponder in token.responders)
         {
           var response : Function = isFaultCall(parameters) ? responder.fault : responder.result;
           response.apply(null, [generateEvent(parameters)]);
         }
 
         //dispatch event to service just in case token wasn't used
         dispatchEvent(generateEvent(parameters));
      }
 
      private function isFaultCall(parameters : Object) : Boolean
      {
         return (_resultData[parameters] is Fault);
      }
 
      private function generateEvent(parameters : Object) : AbstractEvent
      {
         if(isFaultCall(parameters))
         {
            return new FaultEvent(FaultEvent.FAULT, false, true, _resultData[parameters]);
         }
         else
         {
            return new ResultEvent(ResultEvent.RESULT, false, true, _resultData[parameters]);
         }
      }
   }
}

RemoteObjectStub.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package net.digitalprimates.fluint.stubs
{
   import flash.utils.Dictionary;
 
   import mx.rpc.AbstractOperation;
   import mx.rpc.Fault;
   import mx.rpc.remoting.RemoteObject;
 
   public dynamic class RemoteObjectStub extends RemoteObject
   {
      private var _resultData : Dictionary;
 
      //default num of milliseconds to wait before dispatching events
      //don't put too low otherwise your token responders may not be registered
      public var delay : Number = 1000;
 
      public function RemoteObjectStub(destination : String = null)
      {
         super(destination);
         _resultData = new Dictionary();
      }
 
      public function result(methodName : String, args : Array,  data : *) : void
      {
         if(!methodName || methodName.length == 0)
         {
            throw new Error("Cannot use null or empty method names in RemoteObjectStub.");
         }
 
         if(!args)
         {
            args = [];
         }
 
         if(!_resultData[methodName])
         {
            _resultData[methodName] = new Dictionary();
         }
 
         _resultData[methodName][args.toString()] = data;
      }
 
      public function fault(methodName : String, args : Array, code : String, string : String, detail : String) : void
      {
         var fault : Fault = new Fault(code, string, detail);
         this.result(methodName, args, fault);
      }
 
      override public function getOperation(name : String) : AbstractOperation
      {
         return new OperationStub(this, name, _resultData[name]);
      }
   }
}
 
import mx.rpc.AsyncToken;
import net.digitalprimates.fluint.stubs.RemoteObjectStub;
import mx.messaging.config.ServerConfig;
import mx.rpc.Fault;
import flash.utils.Dictionary;
import mx.rpc.remoting.Operation;
import mx.rpc.remoting.RemoteObject;
import flash.events.TimerEvent;
import mx.rpc.events.AbstractEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import flash.utils.Timer;
import mx.rpc.IResponder;
 
internal class OperationStub extends Operation
{
   public var _resultData : Dictionary;
 
   private var token : AsyncToken;
   private var args : Array;
 
   public function OperationStub(remoteObject : RemoteObject, name : String, resultData : Dictionary)
   {
      super(remoteObject, name);
      _resultData = resultData;
   }
 
   override public function send(... args:Array) : AsyncToken
   {
      return configureResponseTimer(args);
   }
 
   private function configureResponseTimer(args : Array) : AsyncToken
   {
      token = new AsyncToken(null);
      this.args = args;
 
      var stub : RemoteObjectStub = RemoteObjectStub(service);
 
      //use a time to give time for the caller to map responders to the asyncToken
      var timer : Timer = new Timer(stub.delay, 1);
      timer.addEventListener(TimerEvent.TIMER_COMPLETE, handleTimer);
 
      timer.start();
 
      return token;
   }
 
   private function handleTimer(event : TimerEvent) : void
   {
      //loop over all responders to emulate a successful call being made
      for each(var responder : IResponder in token.responders)
      {
         var response : Function = isFault(args) ? responder.fault : responder.result;
         response.apply(null, [generateEvent(args)]);
      }
 
      //send the result event to the RemoteObject as well
      service.dispatchEvent(generateEvent(args));
   }
 
   private function isFault(args : Array) : Boolean
   {
      return (_resultData[args.toString()] is Fault);
   }
 
   private function generateEvent(args : Array) : AbstractEvent
   {
      if(isFault(args))
      {
         return new FaultEvent(FaultEvent.FAULT, false, true, _resultData[args.toString()]);
      }
      else
      {
         var result : * = _resultData[args.toString()];
         return new ResultEvent(ResultEvent.RESULT, false, true, _resultData[args.toString()]);
      }
   }
}

UPDATE: – Thanks to Joel Hooks for exercising these a bit more and figuring out a GC issue with the timer that provided some flukely behavior. The above code now supports his fixes in both HTTPServiceStub and RemoteObjectStub. I’ve also run my unit tests against the latest Flex 4 beta and everything passed. Looks like these may be more useful as we start to use Flex 4.



Tags: , , , , ,

Related posts

Discussion

  1. mitch gart Says:

    I was also trying to extend RemoteObject. The problem I ran into was when I tried to use one from mxml code:

    The compiler doesn’t like the inside the RemoteObjectStub. It says:

    Could not resolve to a component implementation.

    Have you figured out a way to use your RemoteObjectStub in mxml with methods on the object?

  2. mitch gart Says:

    The software just mangled the comment that I tried to post. The mxml code had a mxml declaration of a RemoteObjectStub, inside angle brackets, and nested in the RemoteObjectStub was

    (left-angle-bracket)mx:method name=”foo” /(right-angle-bracket)

  3. Brian LeGros Says:

    @mitch – Thanks for stopping by. I’ve only used my implementations in ActionScript, not MXML. I ended up extending the classes in mx.rpc.http and mx.rpc.remoting. I think the classes which are intended for us in MXML are in the mx.rpc.http.mxml and mx.rpc.remoting.mxml packages and have a few more defaults setup than the ones in the higher-level packages. I haven’t played with the mxml classes however, so I’d imagine they are similar to the ones I’ve stubbed out. If you’re interested in seeing support for them, add a comment to the issue on the Fluint Google Group @ http://code.google.com/p/fluint/issues/detail?id=38 so we know people are interested. Hope this helps.

  4. Umar Farook Says:

    Hi i am new to fluint framework.My requirement is whenever I click on a button an
    event is triggered which pulls the data from the database and populates my datagrid.
    Is there a way to test this kind functionality.If so can any one provide an example
    for the same.

    Thanks in advance.

  5. Brian LeGros Says:

    @Umar – Thanks for stopping by. Fluint is definitely capable of writing an integration test that can do what you’re asking. There are a couple ways you can test your datagrid to see if it’s working correctly:

    1. You can Unit Test the button and datagrid by themselves using mocks/stubs to verify that each component is working as expected independent of each other.

    2. You can integration test the button and datagrid together to confirm the components work when placed together.

    3. You can functionally test the button and datagrid together to confirm they work from the user perspective.

    Sounds like #2 or #3 would do what you are asking. #2 can be accomplished with Fluint and #3 can be accomplished with something like FlexMonkey or the Selenium Flex plugin. Usually functional tests are written to confirm requirements and specs, whereas integration tests are tools for development to confirm things are working together.

    To see how to write an integration test using Fluint, check out the Fluint wiki. It’s got lots of great documentation. The example that may help you is how to write an asynchronous test (http://code.google.com/p/fluint/wiki/AsyncTest).

    Hope this helps.

  6. Scott Moore Says:

    Brian,

    Thanks for posting the code! I’m working through setting up some unit tests for a Flex app that uses HTTPService and the HTTPServiceStub has provided useful guidance.

    Question: in your example, you use a method, asyncHandler() that I’m presuming is on FlexUnit’s TestCase object. However, I don’t see this method on TestCase in the current FlexUnit SWC I’m using (version unknown–someone on my team pulled it down a long time ago, never recorded the version), nor do I see it in FlexUnit 0.9 available at http://opensource.adobe.com/wiki/display/flexunit/Downloads .

    What would you recommend? I do see some classes in FlexUnit 0.9 related to async tests–perhaps theres something there I should use instead of asyncHandler()?

    Thanks,
    Scott

  7. Brian LeGros Says:

    @Scott – Thanks for stopping by. The examples using the stubs are written using Fluint 1.x; I’m unsure of the synax that would be required for FlexUnit. Fluint has traditionally had better support for async testing that FlexUnit from what I’ve heard, but the Fluint team has joined forced with the FlexUnit team to make FlexUnit 4 which has some great features including Fluint’s async testing support. It’s currently in beta, but worth using IMO. Hope this helps; glad to hear the stubs are working out.

  8. Scott Moore Says:

    Brian,

    Aha! Thanks for the quick reply.

    Yeah, I probably should have read a bit closer, and noticed that you introduce the code samples with “…in a test case written using fluint:”

    Thanks,
    Scott

  9. Joel Hooks Says:

    I’ve been using this in a project, and found that the inline timer function with weak references fails unexpectedly on occasion. Moving this to a named function with strong reference has solved the issue. I thought perhaps it was the timing, but it would stall even with the timer value set fairly high (3000ms+).

    here are the changes I’ve made, it is much more reliablle for me now:

    http://github.com/joelhooks/utilities-AsyncServiceStubs/commit/a317e3a1303d8b4508d34d1026525324ba617911#diff-2

  10. Brian LeGros Says:

    @Joel – Great catch. I totally missed it; when the method completes it’s going to be marked to be cleaned up and if the handler hasn’t executed yet, it’ll be cleaned up as well since I’m using a weak reference. I have updated the post to reflect your changes. I’m hoping to include these in mock-as3′s latest rendition so we have a way to distribute them. If you have any feedback on how they could be improved, please don’t hesitate to let me know.

    Thanks again for the help. It’s great to hear these have helped some people.

  11. Bhuvana Says:

    Hi,
    This blog entry is very useful for me….
    I want to write test case for my application.
    and my code is below.

    var req :mainRequest = new mainRequest();

    req.clearSession();

    ***************

    public function clearSession():void
    {

    var _remoteHttp : HTTPService = new HTTPService();
    _remoteHttp.url = ApplicationCommonModel.getInstance().serverContextUrl+’logout’;
    _remoteHttp.method = “POST”;
    _remoteHttp.resultFormat = “e4x”;

    _remoteHttp.addEventListener(
    ResultEvent.RESULT, function (event:ResultEvent):void{
    validateUser(mainResult.getInstance()._loginParams);
    } );
    _remoteHttp.addEventListener(
    FaultEvent.FAULT, function (event:FaultEvent):void{
    validateUser(mainResult.getInstance()._loginParams);
    } );
    _remoteHttp.send();

    }

    could u please tel me how write test case for this block of code.?

    Thanks & Regards,
    Bhuvana

  12. Brian LeGros Says:

    @Bhuvana – Thanks for stopping by.

    Based on the example code above, I’d suggest 2 integration tests using the HTTPServiceStub to test clearSession(); one for success and one for failure. The [Before], or setUp(), for each should setup the stub just as you have at the begging of the clearSession() function, minus the event listeners. Since you don’t use any data from the event (from what I can tell from your example code), the success test should call result(null, null) on the stub instance which will generate a ResultEvent. For failure, call fault(null, null, null, null) on the stub instance which will generate a FaultEvent.

    From there, for good practice, you should probably have another test that mocks out validateUser() and mainResult so that you know when clearSession() or its dependencies are broken. There are good mocking frameworks out there. I like mock-as3, but there is also mockito-flex, and asmock. If you’re not familiar with mock objects, check out http://mockobjects.com and http://martinfowler.com/articles/mocksArentStubs.html.

    Best of luck.

  13. Bhuvana Says:

    Thank you very much for your immediate reply.
    could you please write a sample code for that block of code.??
    I am new to flex as well as flexunit.

    Regards,
    Bhuvana

  14. Brian LeGros Says:

    @Bhuvana – Couple of more suggestions that I overlooked from your comment. Currently, clearSession() isn’t testable, since it creates its own HTTPService class. You may want to pass the httpService into the clearSession() method so that you can inject the HTTPServiceStub for testing. Use the example in this blog post as a guideline, substituting ClassWithHTTPServiceDependency for your clearSession() function. If you would like to learn more about FlexUnit4 and how to write unit tests you can check out http://opensource.adobe.com/wiki/display/flexunit/FlexUnit+4+feature+overview and http://opensource.adobe.com/wiki/display/flexunit/Developer+Documentation.

  15. Link Dump: 022210_1928 Says:

    [...] cookies and code » Blog Archive » Using Stubs for HTTPService and RemoteObject in Flex – [...]

  16. ropp Says:

    Great job! It’s just what I’m looking for. But a small issue is that the RemoteOperationStub can not response to its event listeners. Some times we need to add event listeners for each operation instead of adding all event listeners to remote object itself.
    I made some change to support adding event listenrs for operation:
    ——————————————-
    result() function of RemoteObjectStub:

    methods[methodName].push(new RemoteObjectSignature(args, data));
    //added by ropp operations[methodName]=new RemoteOperationStub(this, methodName, methods[methodName]);

    ——————————————
    getOperation() function of RemoteObjectStub:

    //return new RemoteOperationStub(this, name, methods[name]);
    // added by ropp
    return operations[name];

    ——————————————-
    handleTimer() function of RemoteOperationStub:

    //send the result event to the RemoteObject service.dispatchEvent(resultEvent);
    // added by ropp this.dispatchEvent(resultEvent);

    I think it will work better after doing above change.

    Best wishes!

Add A Comment