6 Replies Latest reply on May 29, 2010 10:47 AM by mlabriola

    AsyncToken Unit testing with Mocks

    deshartman Level 1

      Hi,

       

      I have been working with mockalate and trying to find a way to unit test my login code. The problem I am running into is that although the code works when I run the app, the unit tests are failing for some unknown reason.

       

      There are a few parts to the actual test and they are:

       

      Swiz: Calls a delegate function and returns an AsyncToken

      Mocolate: Mocks the delegate function and returns an ASyncToken ResultResponse.

       

      From what I have been able to troubleshoot, the app code itself handles the mock response correctly and assigns the login vales in the code under test, but as soon as I get back into the unitTest code, the values are blank. I see that there is a scopechain where all the values are, but they are cleared in the unit. Inside the code being tested, the mock performs as required and the model.username is set, but outside the app code, the value is blank? Basically, I have hit a brick wall in trying to mock this. Could someone help out?

       

      Thanks

      Des

       

       

      Code for reference:

      ===============

       

              // Deal with a login request
              [Mediate(event="AuthenticationEvent.LOGIN")]
              public function login(authenticationEvent:AuthenticationEvent):void {

       

                  // Handle successful login.
                  var loginResultEvent:Function = function(event:ResultEvent ):void  {
                 
                      // Write the username to the model for convenience
                      loginModel.username = authenticationEvent.username;
                     
                      eventDispatcher.dispatchEvent(new AuthenticationEvent(AuthenticationEvent.LOGGEDIN));
                  }
                 
                  // Handle login failure.
                  var loginFaultEvent:Function = function(faultEvent:FaultEvent):void {
                      eventDispatcher.dispatchEvent(new AuthenticationEvent(AuthenticationEvent.LOGIN_FAIL));
                  }

       

                  // Log into the Remote server channelset login
                  serviceHelper.executeServiceCall(loginDelegate.login(authenticationEvent.username,authent icationEvent.password),
                      loginResultEvent, loginFaultEvent);

       

              }

       

       

      This is called by the UnitTests

      =======================

       

      [Before]
              public function runBeforeEveryTest():void
              {
                  fixture = new LoginController();
                 
                  // Mock up the delegate
                  fixture.loginDelegate = loginDelegateMock;

                  fixture.loginModel = new LoginModel();

                  fixture.eventDispatcher = new EventDispatcher();

              }

       

          [Test(async)]

              public function loginTest_with_valid_user():void
              {
                  // Create the authenticationEvent the Presenter would have sent
                  authenticationEvent = new AuthenticationEvent(AuthenticationEvent.LOGIN);
                  authenticationEvent.username = testUsername;
                  authenticationEvent.password = testPassword;
                 
                  var result:Object = testURLCode;
                  var token:AsyncToken = new AsyncToken();
                  var resultEvent:ResultEvent = new ResultEvent(ResultEvent.RESULT, false, false, result, token);
                 
                  // Mock the response
                  mock(loginDelegateMock).method("login").args(authenticationEvent.username, authenticationEvent.password).returns(token).answers(new ResultAnswer(token, resultEvent));
                 
                  //execute the method
                  fixture.login(authenticationEvent);
                 
                  Assert.assertEquals("Username is in Model", testUsername, fixture.loginModel.username);
              }

       

       

      Trace from run

      ===========

       

      Username is in Model - expected:<a@b.com> but was:<>
      at flexunit.framework::Assert$/failWithUserMessage(Assert.as:591)
      at flexunit.framework::Assert$/failNotEquals(Assert.as:394)
      at flexunit.framework::Assert$/assertEquals(Assert.as:98)
      **[ at com.wl.sfx.unit.controller::LoginControllerTests/loginTest_with_valid_user(LoginControlle rTests.as:141) ]
      at Function/http://adobe.com/AS3/2006/builtin::apply
      at flex.lang.reflect::Method/apply(Method.as:208)
      at ReflectiveCallable/run(FrameworkMethod.as:297)
      at org.flexunit.runners.model::FrameworkMethod/applyExplosivelyAsync(FrameworkMethod.as:171)
      at org.flexunit.runners.model::FrameworkMethod/invokeExplosivelyAsync(FrameworkMethod.as:186 )
      at org.flexunit.internals.runners.statements::InvokeMethod/evaluate(InvokeMethod.as:77)
      at org.flexunit.internals.runners.statements::ExpectAsync/evaluate(ExpectAsync.as:544)
      at org.flexunit.internals.runners.statements::StackAndFrameManagement/handleTimerComplete(St ackAndFrameManagement.as:141)
      at flash.events::EventDispatcher/dispatchEventFunction
      at flash.events::EventDispatcher/dispatchEvent
      at flash.utils::Timer/tick

        • 1. Re: AsyncToken Unit testing with Mocks
          mlabriola Level 4

          Any chance you could get me a minimal project with this code so I could set some breakpoints? It is probably something I could help figure out quickly.

           

          Thanks,

          Mike

          • 2. Re: AsyncToken Unit testing with Mocks
            deshartman Level 1

            Ok, I'll build one and post it, thanks


            • 3. Re: AsyncToken Unit testing with Mocks
              deshartman Level 1

              There does not appear to be a way to attach code?

               

              Des

              • 4. Re: AsyncToken Unit testing with Mocks
                deshartman Level 1

                I sent the code to the private mail. Did you get it?

                 

                Thanks

                Des

                • 5. Re: AsyncToken Unit testing with Mocks
                  mlabriola Level 4

                  Des,

                   

                  No. I did not receive anything.

                   

                  just email me malabriola at that google email thingy with your example and I will be happy to take a look at it.

                   

                  Mike

                  • 6. Re: AsyncToken Unit testing with Mocks
                    mlabriola Level 4

                    So, there are a bunch of issues here. I will try to explain the major points, however, you will have to forgive me as Swiz is still on my list of things to go learn more about. After this post, I am going to reach out to the folks at Swiz and see if they have time to help me prep a best practices around testing Swiz with FlexUnit.

                     

                    Here are the basics:

                     

                    Putting the async in the [Test(async)] metadata opens up access to asynchronous APIs. There is some extra overhead to testing asynchronously, so we try not to add this code to every test, but rather just to the ones requiring the functionality. However, the async attribute does not inherently do anything else. It doesn't make your test asynchronous, it just allows you to now use asynchronous APIs that you otherwise could not.

                     

                    The bigger problem though is that Flash Player is a single threaded machine and, hence, so is FlexUnit. In your code, you do fixture.login() which is intended to execute a service call... an inherently asynchronous event. However, on the next line of code, you do an assertion. The line of the code that does the assertion executes immediately after your call to fixture.login() and hence long before the service call returns. You can confirm this with a few breakpoints and observing the order.

                     

                    The next big problem though is reporting back to FlexUnit. When FlexUnit handles an asynchronous event, it does so by wrapping. So, let's say you have a service class which is going to dispatch an event when an operation is done. You want that event, let's call it EVENT_A to call METHOD_B. How FlexUnit works is that you setup FlexUnit to listen for EVENT_A and then call METHOD_B once the event occurs. This allows FlexUnit to manage the timeout, and, most importantly call METHOD_B in a protected way.

                     

                    When you do an assertion or perhaps your code fails and throws an error, the error proceeds up the call stack. With Flash Player asynchronous operations, the call stack restarts at each new event. Meaning that if you look at the call stack in a method called from a service event (or even a timer) you will only see a few levels deep of calls. If an error is thrown at that moment, it is up to the levels above it to catch the error and do something with it. In these occasions, FlexUnit is not one of those items in the call stack and, as such, cannot catch and report the error.

                     

                    So, FlexUnit has to stay in the middle of each async operation. With the way you have things setup presently, I am not sure where we could be in the middle of that and hence the reason I will ask the Swiz folks for a bit of time as well.

                     

                    Hope that at least helps explain the situation.

                    Mike