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