archived posts

Unit Testing Flex – Alerts

In flexunit, testing on August 19, 2009 at 9:04 pm

If you ever have a serious attempt at unit testing Flex code sooner or later you are going to need to test something that involves user confirmation. Lets take a simple example of deleting critical business information, the last thing we want is to have a user accidently click the wrong button and delete something they didn’t mean to.

package com.compact {
  import mx.controls.Alert;
  import mx.events.CloseEvent;

  public class CustomerListModel {
    public var wasDeleted:Boolean = false;
    public function deleteCustomer(customer:Customer): void {
      Alert.show(
        "Are you sure you want to delete?",
        "Confirm",
        Alert.YES + Alert.NO,
        null,
        function(e:CloseEvent): void {
          if(e.detail == Alert.YES) {
            wasDeleted = true;
          }
        }
      );
    }
  }
}

Now, lets say we wanted to write a unit test so that we can be 100% sure that our important information is only going to be deleted if the user confirms that is what they want to do.

package com.compact {
  import flexunit.framework.TestCase;

  public class CustomerListModelTest extends TestCase {
    private var _model:CustomerListModel;
    override public function setUp(): void {
      _model = new CustomerListModel();
    }
    public function testDeleteSavesIfUserConfirms(): void {
      // wait a minute, damn i'm screwed!
      assertEquals(true, _model.wasDeleted);
    }
    public function testDeleteDoesNotSaveIfUserDoesNotConfirm(): void {
      // wait a minute, damn i'm screwed!
      assertEquals(false, _model.wasDeleted);
    }
  }
}

Ok we didn’t get very far! The main problem with trying to test this method is that Alert.show() will only invoke our close handler if a user physically clicks the alert. We simply have no way to control or replace the actual behaviour of the alert with something that we can simulate for testing.

This is a common problem that you will encounter with unit testing, no matter which language you are using. Fortunately there is a very simple design pattern you can apply to solve it, the adapter pattern.

First we create an interface.

package com.compact {
  public interface Confirmer {
   /**
    * Obtain user confirmation and call the appropriate functions.
    *
    * @param onYes Invoked if the user responds yes.
    * @param onNo Invoked if the user responds no.
    * @param onComplete Invoked regardless of the response.
    */
    function confirm(
      onYes:Function,
      onNo:Function=null,
      onComplete:Function=null):void;
  }
}

And an implementation.

package com.compact {
  import flash.display.Sprite;

  import mx.controls.Alert;
  import mx.events.CloseEvent;

  public class AlertConfirmer implements Confirmer {
    public var title:String = "Confirm";
    public var message:String = "Are you sure you want to delete?";
    public var parent:Sprite = null;
    public function confirm(
      onYes:Function,
      onNo:Function=null,
      onComplete:Function=null):void {

      Alert.show(
        message,
        title,
        Alert.YES + Alert.NO,
        parent,
        function(e:CloseEvent): void {
          if(e.detail == Alert.YES) {
            onYes();
          } else if(onNo != null) {
            onNo();
          }
          if(onComplete != null) {
            onComplete();
          }
        }
      );
    }
  }
}

And then we can change our model to use the new adapter.

package com.compact {
  import mx.controls.Alert;
  import mx.events.CloseEvent;

  public class CustomerListModel {
    public var confirmer:Confirmer = new AlertConfirmer();
    public var wasDeleted:Boolean = false;
    public function deleteCustomer(customer:Customer): void {
      confirmer.confirm(
        function(): void {
          wasDeleted = true;
        }
      );
    }
  }
}

So now we are back were we started with one exception, our alert is hidden behind a well defined interface for obtaining confirmation. This gives us one very significant benefit, we can create a mock (fake) confirmer that we can use for testing.

package com.compact {
  public class MockConfirmer implements Confirmer {
    private var _confirm:Boolean;
    public function MockConfirmer(confirm:Boolean) {
      _confirm = confirm;
    }
    public function confirm(
      onYes:Function,
      onNo:Function=null,
      onComplete:Function=null):void {

      if(_confirm) {
        onYes();
      } else if(onNo != null) {
        onNo();
      }
      if(onComplete != null) {
        onComplete();
      }
    }
  }
}

And now, finally we can complete our unit test.

package com.compact {
  import flexunit.framework.TestCase;

  public class CustomerListModelTest extends TestCase {
    private var _model:CustomerListModel;
    override public function setUp(): void {
      _model = new CustomerListModel();
    }
    public function testDeleteSavesIfUserConfirms(): void {
      _model.confirmer = new MockConfirmer(true);
      _model.deleteCustomer(new Customer());
      assertEquals(true, _model.wasDeleted);
    }
    public function testDeleteDoesNotSaveIfUserDoesNotConfirm(): void {
      _model.confirmer = new MockConfirmer(false);
      _model.deleteCustomer(new Customer());
      assertEquals(false, _model.wasDeleted);
    }
  }
}

Successful unit test for alert confirmation.
Excellent! Lets summarise what we have done:

We used the adapter pattern to create a wrapper around something that is difficult to test. We created a default implementation of the adapter that we will use to replace our original implementation. We then created a very basic mock object that allows us to simulate the behaviour of the adapter so that we can easily test it.

Congratulations you have just learned how to unit test alert confirmations in flex. But more importantly you have learned one of the most common techniques for testing code that is otherwise untestable.

Note: To be completely thorough you will need to manually test the default implementation of the adapter to make sure it works.

  1. Just a note, decoupling the Alert code from the CustomerListModel is a good step, but there are tools out there that can be used to avoid you having to create an interface and 2 implementations classes just to support testing. mock-as3, asmock, and mockito-flex all support type-safe object mocking. In this example, you could just create AlertConfirmer alone and then stub out the behavior of the confirm() method for testing.

    I personally prefer using mock-as3 since it’s extremely simple to use; the type-safe mocking version hasn’t been officially released as of yet, so you can find a copy of it in this sample project I did for a demo:

    http://svn.adogo.us/200908/SampleTestsUsingFU4MockAs3/libs/

    Also you should look into using flexunit4 instead of the original flexunit. The async support is much more robust and there are more tools to do integration testing ala what was in the fluint library. FlexUnit4 is in beta2 now and it’s quite stable.

    Keep pushing the Flex unit testing love, the community needs more great examples.

  2. Hi Brian,

    Glad that you pulled me up on not using a mocking framework. I’m actually using mockito-flex in my day to day work and really liking it! The main problem with auto mocking in the example is the fact that I’m passing an anonymous function to the confirmer:

    function(): void {
    wasDeleted = true;
    }

    As part of my test I need to make sure this function is invoked to verify that it works. I couldn’t find a way to do this with mockito-flex.

    Just for you I’ll have a go using FlexUnit 4 for my next flex post 🙂

    Thanks,
    Shanon

  3. @Shanon – If you use a mocking framework and set an expectation that confirm() should be called, you can verify that the method was called by deleteCustomer using expectations and feel good that your class is interacting with its dependencies correctly. The anonymous function is just help to stub the confirm method, so don’t feel dirty for using it, all you care about is that confirm is being called correctly. In mock-as3, this would be written something like:

    mock.method(“confirm”).calls(function () : void {
    wasDeleted = true;
    }).once;
    mock.verify();

    The idea of using a mock object framework is not only to stub the return value or error being thrown, but set expectations on how the dependencies should be used using withArgs() and methods to determine the # of calls for a method/properties on a dependency (once/twice/exactly).

    Hope you enjoy FlexUnit4.

  4. I guess the only concern regarding that approach is that if I stub the confirm method/function then I would never actually test the real implementation. I’m really into TDD so I try my hardest to have a failing test in front of me before I write any production code. In any case I accept that the majority of times using a mocking framework is a preferable approach.

    I’m currently looking at putting together a small sample application to serve as a guide for those interesting in unit testing flex code. I’m looking at getting stuck into that this week, time permitting. If you have any ideas or thoughts on what should be included or you would like to get involved, send me an email @ shanonmcquay@gmail.com.

  5. @Shannon – In your example, you’re testing CustomerListModelTest. The mocking framwork is useful to emulate dependencies on the class you’re testing. If you wanted to test AlertConfirmer, you’d have to write a separate test (e.g. – AlertConfirmerTest). If you find a mocking framework that supports static method mocking, then you can mock Alert.show() in that AlertConfirmerTest. From there no need to test Alert.show() it’s a framework method for which you’re not responsible.

    You can still practice TDD this way. Once you saw the need for the AlertConfirmer, you could have written the test for AlertConfirmer and then implemented it. In fact the mocking framework allows you to mock AlertConfirmer so you can finish the test for CustomerListModelTest before you even tackle implementing CustomerListModel and then you could go on to write AlertConfirmerTest and then AlertConfirmer.

    In terms of a unit testing guide, I’d suggest checking out the wiki for the fluint testing framework and flexunit4. THere are a lot of great examples showing how to perform Theory and DataPoint tests, SequenceSteppers, Async testing, etc. Best of luck on the endeavor.

    • I apologize for not making myself clear. In my previous posts I am talking about my dislike for this piece of code:

        mock.method(”confirm”).calls(
          function () : void {
            wasDeleted = true;
          }
        ).once;
        mock.verify();
      

      My problem is that this does not provide adequate coverage of the production code. It tests a copy of the production code. The reason this is a problem is that I could change my production code to look like this….

        public function deleteCustomer(customer:Customer): void {
          confirmer.confirm(
            function(): void {
              throw new Error('My production code is broken');
            }
          );
        }
      

      And my test would still pass.

  6. @Shanon – I agree with you, when you pass functions around, testing becomes limited to what executes the code, rather than what the code itself does. If this is the case, why not just utilize a method from the scope chain that can be mocked on an object. This way you guarantee the same behavior on each call because you’re using a predefined method.

Leave a comment