Dahlia Bock

29Oct/090

Testing: Unit testing on the UI in Flex (Part 3)

So how does the view model for the test in my previous post look like? Judging from the test cases written, we would have 3 public methods:

  1. viewModel.valid
  2. viewModel.hasDuplicates
  3. viewModel.save which depends on the first two
public class SomeViewModel
    {
        public var dispatcher:IEventDispatcher;

        [Bindable]
        public var subscriptions:ArrayCollection;

        private var subscriptionsFromServer:ArrayCollection;

        public function SomeViewModel() {}

        public function addNewSubscription():void
        {
            if(subscriptions == null)
                subscriptions = new ArrayCollection();

            subscriptions.addItem(SomeViewModel.createNewViewModel());
        }

        public function get valid():Boolean
        {
            if(subscriptions == null)
                return false;

            for each(var subscription:SomeViewModel in subscriptions)
            {
                if(!subscription.valid)
                    return false;
            }
            return true;
        }

        public function get hasDuplicates():Boolean
        {
            for each(var subscription:SomeViewModel
                                                 in subscriptions)
            {
                var remainingSubscriptions:ArrayCollection =
                                             removeFirstOccurenceOf(subscription);
                if(containsDuplicateValues(remainingSubscriptions, subscription))
                    return true;
            }
            return false;
        }

        public function save():void
        {
            if(hasChanges && valid)
            {
                var subscriptions:ArrayCollection =
                         SomeViewModel.convertToDomainModels(subscriptions);
                var event:SomeEvent =
                         SomeEvent.createSaveSubscriptionsEvent(subscriptions);
                dispatcher.dispatchEvent(event);
            }
        }

    }

Since Mate is event-based, the act of clicking on the 'Save' button causes the dispatching of an event, and the dispatcher is always the view, which is injected to the View Model via the Controller.

At the end of this, we have the functionality that pertains to the 'Save' button, but the view doesn't need know about these complications. The controller will wire up the 'Save' button to invoke the save method on the view model, which in turn will only dispatch the event back to the controller if there is a need.

29Oct/090

Testing: Unit testing on the UI in Flex (Part 2)

Continuing on my previous post, say we have the following view:

View

In this screen, a user is allowed to add a new subscription in the system that s/he cares about, view existing subscriptions, remove existing subscriptions and subsequently save those subscriptions. Let's focus on a small portion of the functionality that is outlined in this view. When the user adds new subscriptions, saving is only allowed if:

  1. All the required fields have been selected
  2. There are no duplicates.

So how do we verify that behavior? Forgetting about the view for a moment, let's think about the presentation model (I will call it view model) class, which holds the state and logic behind the view. There will be a 'save' method on that class which will only perform its magic when the view model itself is valid, which means all required fields have been selected, and there aren't any duplicate subscriptions. So let's write the tests for that behavior:

    public class SomeViewModelTest extends TestCase
    {
        [Test]
        public function shouldNotBeValidIfThereAreNoSubscriptions():void
        {
            //given there are no subscriptions
            //then
            assertFalse(viewModel.valid);
        }

        [Test]
        public function shouldBeValidWhenAllSubscriptionsAreValid():void
        {
            //given
            viewModel.addNewSubscription();
            viewModel.addNewSubscription();

            //when
            for each(var subscription:SomeViewModel in viewModel.subscriptions)
            {
                subscription.type = TestHelper.SUBSCRIPTION_TYPE_A;
                subscription.group = TestHelper.GROUP_2;
                subscription.priority = TestHelper.ACTIONABLE_PRIORITY;
            }

            //then
            assertTrue(viewModel.valid);
        }

        [Test]
        public function shouldNotBeValidIfOneSubscriptionIsNotValid():void
        {
            //given
            viewModel.addNewSubscription();
            viewModel.addNewSubscription();

            //when
            viewModel.subscriptions[0].type = TestHelper.SUBSCRIPTION_TYPE_A;
            viewModel.subscriptions[0].group = TestHelper.GROUP_1;
            viewModel.subscriptions[0].priority = TestHelper.ACTIONABLE_PRIORITY;

            //then
            assertFalse(viewModel.valid);
        }

        [Test]
        public function hasDuplicatesShouldReturnTrueIfThereAreDuplicateTypeASubscriptions():void
        {
            //given
            var subscriptionsFromServer:ArrayCollection = new ArrayCollection();
            subscriptionsFromServer.addItem(helper.
                createSubscriptionTypeADomainModelWithInformationalPriority());
            subscriptionsFromServer.addItem(helper.
              createSubscriptionTypeBDomainModelWithActionablePriority(TestHelper.GROUP_1));
            viewModel.populateSubscriptionsFromServer(subscriptionsFromServer);

            //when
            viewModel.addNewSubscription();
            viewModel.subscriptions.setItemAt(helper.
                   createSubscriptionTypeADomainModelWithInformationalPriority(), 2);

            //then
            assertTrue(viewModel.hasDuplicates);
        }

        [Test]
        public function hasDuplicatesShouldReturnTrueIfThereAreDuplicateTypeBSubscriptions():void
        {
            //given
            var subscriptionsFromServer:ArrayCollection = new ArrayCollection();
            subscriptionsFromServer.addItem(helper.
                createSubscriptionTypeADomainModelWithInformationalPriority());
            subscriptionsFromServer.addItem(helper.
              createSubscriptionTypeBDomainModelWithActionablePriority(TestHelper.GROUP_1));
            viewModel.populateSubscriptionsFromServer(subscriptionsFromServer);

            //when
            viewModel.addNewSubscription();
            viewModel.subscriptions.setItemAt(helper.
              createSubscriptionTypeBDomainModelWithActionablePriority(TestHelper.GROUP_1), 2);

            //then
            assertTrue(viewModel.hasDuplicates);
        }

        [Test]
        public function hasDuplicatesShouldReturnFalseIfThereAreNoDuplicates():void
        {
            //given
            var subscriptionsFromServer:ArrayCollection = new ArrayCollection();
            subscriptionsFromServer.addItem(helper.
                createSubscriptionTypeADomainModelWithInformationalPriority());
            subscriptionsFromServer.addItem(helper.
              createSubscriptionTypeBDomainModelWithActionablePriority(TestHelper.GROUP_1));
            viewModel.populateSubscriptionsFromServer(subscriptionsFromServer);

            //then
            assertFalse(viewModel.hasDuplicates);
        }

    }
}

The view model has 2 attributes that are used to determine whether subscriptions can be saved or not: valid and hasDuplicates. Next we'll take a look at the view model itself and how it ties back to the view.

28Oct/090

Testing: Unit testing on the UI in Flex (Part 1)

I seem to be obsessed with testing lately and after watching J.B. Rainsberger's video, I feel even more convinced that good developer testing plays a big part in producing code that is maintainable and not adverse to change in addition to providing the assurance to the developer that a functionality is done when they say it is.

Testing UI code is often a challenge and most projects I've been on defer that to acceptance tests that are driven from the browser. I'm not disregarding browser tests entirely but like I've mentioned in my previous post, that could be a suboptimal testing option.

Mate (pronounced Mah-tay), which is a tag-based Flex framework, provides the ability to define a separation between the state and behavior of the presentation of a view and the UI controls of the view itself, thus allowing for extensive unit testing of the UI. Before I delve deeper into how that works, let me talk a little bit about the Presentation Model pattern, advocated by Martin Fowler. Given a Model-View-Controller situation, the idea of the pattern is to remove all logic from the view into the presentation model, which is a model class that is part of the presentation (or view). This gives us a few advantages:

  1. The view becomes dumb and simple. No logic resides here, just UI components.
  2. The view could potentially be reused to display other data by just switching out the model.
  3. And more importantly, we are now able to test the behavior of the view (i.e. what happens when a button is clicked, are multiple drop-down menus dependent on each other, and if so, how?)

We will take a look at how to test the behavior and state of the view in my subsequent posts.

Filed under: flex, testing, UI No Comments