compactcode keeping software simple


26
Aug/09
2

Unit Testing Flex – Mocking

If you have experimented with unit testing either recently or in the past you will be aware that sometimes it can get difficult. There are many challenges you might encounter including the following:

  • You need to ensure something happened, without going into detail.
  • You have to do too much to write a single test.
  • You need to test something that you don't have control over.

In this post I want to show some ways to get around these common unit testing roadblocks using a concept called mock objects. Basically a mock object is just an object that can simulate the behaviour of another object in order to make unit tests easier to write.

Imagine yourself working as a developer at an exciting new tech startup...

Your company is launching a new website tomorrow that lets users advertise and sell their cars online. Your boss turns up at your desk at 10 am in the morning, he has a rather worried look on his face. It turns out that he forgot to schedule the feature that actually allows users to publish vehicles to the site. Oops! Your boss tells you to drop everything and get this done by the end of the day because if you don't, your fired!

You look through the code and find that a service method exists that does what you want:

package com.compact.mocking {
  public class VehicleService {
    public function publish(item:Vehicle): void {
      ...
    }
  }
}

Excellent now all we need to do is ensure we call this function from our GUI model.

package com.compact.mocking {
  public class VehicleModel {
    public var service:VehicleService;
    public function publish(item:Vehicle): void {
      service.publish(item);
    }
  }
}

Easy, next on the list is to write a unit test so we are 100% sure that we are calling the right function. This is were things get a little tricky, we know the VehicleService works and we don't need to test it again. All we really need to do is make sure that we call the vehicle service... but after that we don't really care what happens.

You need to ensure something happened, without going into detail.

Using mockito-flex we are going to create a mock version of our service. We will then replace the real service with the mock version. Thanks to mockito-flex our mock will automatically record all invocations it receives. We can then inspect these records to verify if the publish function has been called.

package com.compact.mocking {
  import org.mockito.MockitoTestCase;
 
  // MockitoTestCase extends the standard FlexUnit TestCase. 
  public class CustomerModelTest extends MockitoTestCase {
    private var _model:VehicleModel;
    public function CustomerModelTest() {
      // Tell mockito which classes will need to be mocked.
      super([VehicleService]);
    }
    override public function setUp(): void {
      _model = new VehicleModel();
      // Replace the real service with a mock version.
      _model.service = mock(VehicleService);
    }
    public function testPublishDelegatesToService(): void {
      var item:Vehicle = new Vehicle();
 
      // This should call the publish function of our mock service.
      _model.publish(item);
 
      // Verify that the mock service received the right call.
      verify().that(_model.service.publish(item));
    }
  }
}

Your boss is impressed, you finished ALL that within 5 minutes! Unfortunately the victory is short lived because he just remembered something... that's right you need make sure the vehicle is valid before publishing.

You have a look through the code and find that this validation method exists on the vehicle:

package com.compact.mocking {
  [Bindable]
  public class Vehicle {
    public var make:String;
    public var model:String;
    public var price:Number;
 
    public var ownerName:String;
    public var locationName:String;
 
    public function isValid(): Boolean {
      var valid:Boolean = true;
      valid = valid && notEmpty(make);
      valid = valid && notEmpty(model);
      valid = valid && notEmpty(ownerName);
      valid = valid && notEmpty(locationName);
      valid = valid && price > 0;
      return valid;
    }
    private function notEmpty(value:String): Boolean {
      return value && value.length > 0
    }
  }
}

So you change your model to take advantage of it.

package com.compact.mocking {
  public class VehicleModel {
    public var service:VehicleService;
    public function publish(item:Vehicle): void {
      if(item.isValid()) {
        service.publish(item);
      }
    }
  }
}

And then expertly update your test.

package com.compact.mocking {
  import org.mockito.MockitoTestCase;
 
  public class CustomerModelTest extends MockitoTestCase {
    private var _model:VehicleModel;
    public function CustomerModelTest() {
      super([VehicleService]);
    }
    override public function setUp(): void {
      _model = new VehicleModel();
      _model.service = mock(VehicleService);
    }
    public function testPublishDelegatesToService(): void {
      var item:Vehicle = new Vehicle();
      item.make = "a";
      item.model = "b"
      item.price = 5;
      item.ownerName = "fred";
      item.locationName = "australia"
 
      _model.publish(item);
 
      verify().that(_model.service.publish(item));
    }
  }
}

You get the job done but you feel like you had to do a lot of work to get there. You also remember that your colleague Fred is currently working on adding more conditions to the validation function. That means you will need to add even more setup to this test.

You have to do too much to write a single test.

You wish there was a way to avoid having to do all that work to make your vehicle valid. Wouldn't it be great if you could just create a mock vehicle that is always valid? Well actually... you can!

package com.compact.mocking {
  import org.mockito.MockitoTestCase;
 
  public class CustomerModelTest extends MockitoTestCase {
    private var _model:VehicleModel;
    private var _item:Vehicle;
    public function CustomerModelTest() {
      // We are mocking the vehicle as well.
      super([VehicleService, Vehicle]);
    }
    override public function setUp(): void {
      _model = new VehicleModel();
      _model.service = mock(VehicleService);
      // Create the mock vehicle.
      _item = mock(Vehicle);
    }
    public function testPublishDelegatesToService(): void {
      // Update the mock so the isValid() function always return true. 
      given(_item.isValid()).willReturn(true);
 
      _model.publish(_item);
 
      verify().that(_model.service.publish(_item));
    }
  }
}

Your boss is definitely impressed now.. you just cut five lines out of that test and when Fred updates the validation function.. your test won't break!

Everything is going well as you get ready for the big release, it's 3pm and your just about to celebrate over a few drinks when your boss remembers something... You need to record the time that every vehicle was published, because you are planning on charging your users 50c for each day they have their vehicle on your website.

Amazingly it turns out this functionality is already there on the service!

package com.compact.mocking {
  public class VehicleService {
    public function publish(item:Vehicle): void {
      ...
    }
    public function recordPublishTime(item:Vehicle, time:Date): void {
      ...
    }
  }
}

No problem, we just need to add a call to this function in our model.

package com.compact.mocking {
  public class VehicleModel {
    public var service:VehicleService;
    public function publish(item:Vehicle): void {
      if(item.isValid()) {
        service.publish(item);
        service.recordPublishTime(item, new Date());
      }
    }
  }
}

Ok so that's looking good, except how can we verify this call using mockito?
A first attempt might look like this:

    public function testPublishRecordsPublishTimeUsingService(): void {
      given(_item.isValid()).willReturn(true);
 
      _model.publish(_item);
 
      verify().that(_model.service.recordPublishTime(_item, new Date()));
    }

Unfortunately that won't work. The date we are using in our verify is not the same as the date we are creating in our model because the two dates have different millisecond values. How do we take control of the date creation process so that we can match these dates and complete the test?

You need to test something that you don't have control over.

Fortunately it's not too hard, we just need to introduce a date factory. Once we have this factory, we can then use a mock to precisely control the date creation process. This is what the factory looks like.

package com.compact.mocking {
  public class TimeFactory {
    public function currentTime(): Date {
      return new Date();
    }
  }
}

We then change our model to use the factory instead of directly creating dates.

package com.compact.mocking {
  public class VehicleModel {
    public var service:VehicleService;
    public var timeFactory:TimeFactory = new TimeFactory();
    public function publish(item:Vehicle): void {
      if(item.isValid()) {
        service.publish(item);
        service.recordPublishTime(item, timeFactory.currentTime());
      }
    }
  }
}

Now we can fairly easily mock the factory and complete the test.

package com.compact.mocking {
  import org.mockito.MockitoTestCase;
 
  public class CustomerModelTest extends MockitoTestCase {
    private var _model:VehicleModel;
    private var _item:Vehicle;
    public function CustomerModelTest() {
      super([VehicleService, Vehicle, TimeFactory]);
    }
    override public function setUp(): void {
      _model = new VehicleModel();
      _model.service = mock(VehicleService);
      _model.timeFactory = mock(TimeFactory);
      _item = mock(Vehicle);
    }
    public function testPublishRecordsPublishTimeUsingService(): void {
      var publishDate:Date = new Date();
 
      given(_item.isValid()).willReturn(true);
      given(_model.timeFactory.currentTime()).willReturn(publishDate);
 
      _model.publish(_item);
 
      verify().that(_model.service.recordPublishTime(_item, publishDate));
    }
  }
}

Phew! You did it.

You just saved the company! You completed the feature that allows your customers to publish their vehicles onto your new website. You unit tested your code and did so in an elegant way using mock objects and the mockito-flex framework. You encountered three common problems on your journey and overcame them all. Now all you need to do is continue the good work you have been doing and try your hand writing more unit tests.

Filed under: testing
19
Aug/09
7

Unit Testing Flex – Alerts

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.

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
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.

Filed under: testing
14
Aug/09
1

Unit Testing Flex – States

This is a quick post to share a solution to a problem I discovered while trying to unit some test some Flex code that involved states.

FormWithStates.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:states>
    <mx:State name="advanced">
      <mx:AddChild relativeTo="{nameItem}" position="after">
        <mx:FormItem label="Age">
          <mx:TextInput id="ageText"/>
        </mx:FormItem>
      </mx:AddChild>
    </mx:State>
  </mx:states>
  <mx:Form width="100%">
    <mx:FormItem id="nameItem" label="Name">
      <mx:TextInput id="nameText"/>
    </mx:FormItem>
    <mx:FormItem>
      <mx:Button label="Search"/>
    </mx:FormItem>
  </mx:Form>
</mx:HBox>

I was trying to reference the ageText component from my unit test like this:

FormWithStatesTest.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.compact {
  import flexunit.framework.TestCase;
 
  public class FormWithStatesTest extends TestCase {
    private var _view:FormWithStates;
 
    override public function setUp():void {
      _view = new FormWithStates();
      _view.initialize();
    }
 
    public function testCanSetAgeText():void {
      _view.ageText.text = "foo";
      assertEquals("foo", _view.ageText.text);
    }
  }
}

Unfortunately every time I ran the test it would fail:

Trying to use a component created in an mxml state can be tricky.

Null pointer exception. Ok thats because by default all the objects in an mxml state are created lazily. All we needed to do was to find a way to manually make sure my states are being initialized. I searched around the API for a while but failed to find anything obvious that would do the job. In the end I decided to do a bit of a hack and initialize all the components inside the state manually. This is what the end result looked like:

FormWithStatesTest.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
package com.compact {
  import flexunit.framework.TestCase;
 
  import mx.core.UIComponent;
  import mx.states.IOverride;
  import mx.states.State;
 
  public class FormWithStatesTest extends TestCase {
    private var _view:FormWithStates;
 
    override public function setUp():void {
      _view = new FormWithStates();
      _view.initialize();
      initializeStates(_view);
    }
 
    public function initializeStates(component:UIComponent):void {
      for each (var _state:State in component.states) {
        for each (var _override:IOverride in _state.overrides) {
          _override.initialize();
        }
      }
    }
 
    public function testCanSetAgeText():void {
      _view.ageText.text = "foo";
      assertEquals("foo", _view.ageText.text);
    }
  }
}

Boom. Tests pass. I feel that since its a Friday afternoon I should celebrate, beer anyone? ;)

Filed under: testing
13
Aug/09
0

Flex Snippet – Automatic Dirty Checking

In order to prepare for an upcoming tutorial on flex unit testing I need to present a couple of simple concepts that I can build upon to create some useful example code.

The first of these is the concept of automatic dirty checking. You can use dirty checking to determine if any properties on given object have changed since you last looked at it. This technique is quite common in applications where users are editing mission critical data. It can be used to prevent users from forgetting to save something they have changed. An example is probably the easiest way to explain it:

public function close(): void {
  if(importantDataWasChanged) {
    var saveFirst:Boolean = askUserIfTheyWantToSaveBeforeClosing();
    if(saveFirst) {
      saveImportantInfo();
    }
  }
  // perform close
}

There are various ways you can do this but I want to present an approach that is both easy to use and more importantly, easy to test. The first rule of making something easy to test, create an interface:

ChangeDetector.as

1
2
3
4
5
6
7
8
9
package com.compact {
  /**
    * Simple interface for observing changes in a given object.
    */
  public interface ChangeDetector {
    function watch(object:Object):void;
    function get changed():Boolean;
  }
}

Basically we want to use this interface to watch a single object at a time and use the changed property to query if that object has been modified since we began watching. We can achieve this by 'cloning' the object when we call watch and then comparing the clone to the current version of the object.

CloningChangeDetector.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.compact {
  import mx.utils.ObjectUtil;
  /**
    * Uses a clone and compare technique to determine if a given object
    * has changed/is dirty. 
    */
  public class CloningChangeDetector implements ChangeDetector {
    private var _before:Object;
    private var _now:Object;
 
    public function watch(object:Object):void {
      _before = ObjectUtil.copy(object);
      _now = object;
    }
 
    public function get changed():Boolean {
      return ObjectUtil.compare(_before, ObjectUtil.copy(_now)) != 0;
    }
  }
}

And to prove it all works:

CloningChangeDetectorTest.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
package com.compact {
  import flexunit.framework.TestCase;
 
  public class CloningChangeDetectorTest extends TestCase {
    private var _detector:ChangeDetector;
 
    override public function setUp():void {
      _detector = new CloningChangeDetector();
    }
 
    public function testChangedReturnsFalseByDefault():void {
      assertEquals(false, _detector.changed);
    }
 
    public function testChangedReturnsFalseIfNothingChanged():void {
      var _watched:Object = new Object();
      _watched.name = "fred"
      _detector.watch(_watched);
      assertEquals(false, _detector.changed);
    }
 
    public function testChangedReturnsTrueIfNameChanged():void {
      var _watched:Object = new Object();
      _detector.watch(_watched);
      _watched.name = "fred"
      assertEquals(true, _detector.changed);
    }
 
    public function testChangedReturnsFalseIfNameChangeReversed():void {
      var _watched:Object = new Object();
      _watched.name = "fred"
      _detector.watch(_watched);
      _watched.name = "bob"
      _watched.name = "fred"
      assertEquals(false, _detector.changed);
    }
 
    public function testDetectsNoChangeOnTypedObjects():void {
      var _watched:Customer = new Customer();
      _detector.watch(_watched);
      assertEquals(false, _detector.changed);
    }
 
    public function testDetectsChangesOnTypedObjects():void {
      var _watched:Customer = new Customer();
      _detector.watch(_watched);
      _watched.name = "fred"
      assertEquals(true, _detector.changed);
    }
  }
}
 
class Customer {
	public var name:String;
}

Voila, your good to go!

Filed under: flex
12
Aug/09
4

Unit Testing Flex – More MXML

This is a continuation of my previous post. Today I am going to show you three more simple techniques you can use to unit test your mxml:

  1. Button Clicks
  2. List Selection
  3. DataGrid Rendering

Button Clicks

ClickForm.mxml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox 
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    xmlns:compact="com.compact.*">
    <compact:ClickModel id="model" />
    <mx:Button 
      id="button" 
      label="Increment" 
      click="model.incrementClickCount()"/>
</mx:HBox>

ClickModel.as

1
2
3
4
5
6
7
8
package com.compact {
    public class ClickModel {
      public var clickCount:int = 0;
      public function incrementClickCount(): void {
        clickCount++;
      }
    }
}

ClickTest.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.compact {
    import flash.events.MouseEvent;	
    import flexunit.framework.TestCase;
    public class ClickTest extends TestCase {
      private var _form:ClickForm;
      override public function setUp():void {
        _form = new ClickForm();
        _form.initialize();
      }
      public function testClickingButtonIncrementsClickCount(): void {
        _form.button.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
        assertEquals(1, _form.model.clickCount);
      }
    }
}

List Selection

SelectionForm.mxml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox 
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    xmlns:compact="com.compact.*">
    <compact:SelectionModel id="model"/>
    <mx:List 
      id="listView" 
      dataProvider="{model.list}" 
      change="model.selected = listView.selectedItem"/>
</mx:HBox>

SelectionModel.as

1
2
3
4
5
6
7
8
package com.compact {
    import mx.collections.ArrayCollection;
    [Bindable]
    public class SelectionModel {
        public var list:ArrayCollection = new ArrayCollection();
        public var selected:Object;
    }
}

SelectionTest.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.compact {
    import flexunit.framework.TestCase;
    import mx.events.ListEvent;
 
    public class SelectionTest extends TestCase {
      private var _form:SelectionForm;
      override public function setUp():void {
        _form = new SelectionForm();
        _form.initialize();
      }
      public function testListViewSelectionUpdatesModel(): void {
        var itemOne:Object = new Object();
        var itemTwo:Object = new Object();
        _form.model.list.addItem(itemOne);
        _form.model.list.addItem(itemTwo);
        _form.listView.selectedItem = itemTwo;
        _form.listView.dispatchEvent(new ListEvent(ListEvent.CHANGE));
        assertEquals(itemTwo, _form.model.selected);
      }
    }
}

DataGrid Rendering

DataGridForm.mxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox 
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    xmlns:compact="com.compact.*">
    <compact:DataGridModel id="model" />
    <mx:DataGrid id="customerGrid" dataProvider="{model.customers}">
      <mx:columns>
        <mx:DataGridColumn id="nameCol" dataField="name"/>
        <mx:DataGridColumn 
          id="birthCol" 
          dataField="birth" 
          labelFunction="model.formatBirth"/>
       </mx:columns>
    </mx:DataGrid>
</mx:HBox>

DataGridModel.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.compact {
  import mx.collections.ArrayCollection;
  import mx.controls.dataGridClasses.DataGridColumn;
  import mx.formatters.DateFormatter;
 
  [Bindable]
  public class DataGridModel {
    [ArrayElementType("com.compact.Customer")]
    public var customers:ArrayCollection = new ArrayCollection();
    private var _format:DateFormatter = new DateFormatter();
    public function formatBirth(item:Object, col:DataGridColumn): String 
      _format.formatString = "MMMM/YYYY";
      return _format.format(item.birth);
    }
  }
}

Customer.as

1
2
3
4
5
6
7
package com.compact {
  [Bindable]
  public class Customer {
    public var name:String
    public var birth:Date;
  }
}

DataGridTest.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.compact {
  import flexunit.framework.TestCase;
 
  public class DataGridTest extends TestCase {
    private var _form:DataGridForm;
    override public function setUp():void {
      _form = new DataGridForm();
      _form.initialize();
      // Initializes the datagrid columns.
      _form.customerGrid.validateProperties();
    }
    public function testNameIsRenderedAsIs(): void {
      var customer:Customer = new Customer();
      customer.name = "foo";
      assertEquals("foo", _form.nameCol.itemToLabel(customer));
    }
    public function testBirthIsRenderedUsingDateFormat(): void {
      var customer:Customer = new Customer();
      customer.birth = new Date(2000, 0, 1);
      assertEquals("January/2000", _form.birthCol.itemToLabel(customer));
    }
  }
}

Do you find this useful? Does it need more explanation? I'm not really sure what people are looking for so I have left this post as lightweight as possible.

As requested my next post will start looking at some slightly more complicated tests and how we can use mock objects to simplify them.

Filed under: flex, testing