Test Features for Flex UI Components with FlexUnit 4
For work, somehow it was required to add functionality to one self-written flex component. At the same time, it was important not to break the already existing behavior, because the component during its existence has been used in several applications and has been overgrown with heirs.
A standard approach to solving such problems is to start by writing unit tests covering the current behavior of the component, while clarifying the features of its device.
Only after this, it is possible to start step-by-step refactoring and expansion of functionality, constantly running tests on the subject if something has broken as a result of changes. [3]
However, the task is complicated by the fact that visual components in flex have a multiphase asynchronous procedure for initializing and updating properties, and they require special tools for writing tests. FlexUnit 4 makes it easy to cope with this task, and below I will show how to do it, and at the same time I will reveal a couple of nuances.
When testing UIComponent descendants, we must take into account that most of the properties of the component are in an unstable state until it is added to the display list and goes through all the initialization stages (createChildren, commitProperties, measure, updateDisplayList, etc.).
If we run the test before this event, then depending on the speed and load of the computer on which the tests are running, we will receive the component in slightly different states. To prevent this, we must wait for the CREATION_COMPLETE event.
Suppose we want to create a button that would not only hide when setting visible = false, but would not affect the location of other components, i.e. includeInLayout = false.
Here, of course, you could includeInLayout = visible and set the set visible function in the function itself, but I want to show a more general case when there are many interconnected properties and all of them need to be updated in commitProperties.
In order to verify “by eye” that everything is working correctly for us, run the test application:
Now try to write the test itself:
1. As you can see from the code, I used the listener on the UIComponent 'includeInLayoutChanged' internal event to catch changes to the includeInLayout property. The FlexUnit documentation [1] recommends using FlexEvent.VALUE_COMMIT, but it is not always dispatched; you have to dispatch it yourself.
In the most general case, when it is difficult to isolate an event after which you can check the properties, you can test the component like any other asynchronous process: set a timer with a guaranteed long interval. Then our test will be rewritten like this:
If you need to do a lot of such tests with a timer, then the initialization and zeroing of the timer must be carried out in the corresponding [Before] and [After] methods.
2. This test was written under Flex Sdk 3. *, if you are working with Flex 4. *, instead of Application.application.addChild (myBtn) you need to use FlexGlobals.topLevelApplication.addChild (myBtn) .
3. The disadvantage of adding test components directly to Application.application is that the component is visible in the test runner window, and also that the test runner styles can affect the styles of your component.
To avoid this, FlexUnit has a special UIImpersonator class that emulates adding a component to the display list.Unfortunately, I could not get UIImpersonator to work in Ant-builds and from Intellij - I get the error “Timeout Occurred before expected event”. Maybe someone will tell you why. For an example of how to use UIImpersonator, see the comments below.
UPD: To work from Intellij and ant, you need to remove flexunit-4.1.0-33-as3_3.5.0.12683.swc from the list of connected libraries. It conflicts with the same for flex.
1. FlexUnit dock: docs.flexunit.org/index.php?title=Main_Page
2. Creating and running unit tests from FlashBuilder: habrahabr.ru/blogs/Flash_Platform/89487
3. Refactoring reference book: Michael Feathers. Working Effectively with Legacy Code.
A standard approach to solving such problems is to start by writing unit tests covering the current behavior of the component, while clarifying the features of its device.
Only after this, it is possible to start step-by-step refactoring and expansion of functionality, constantly running tests on the subject if something has broken as a result of changes. [3]
However, the task is complicated by the fact that visual components in flex have a multiphase asynchronous procedure for initializing and updating properties, and they require special tools for writing tests. FlexUnit 4 makes it easy to cope with this task, and below I will show how to do it, and at the same time I will reveal a couple of nuances.
When testing UIComponent descendants, we must take into account that most of the properties of the component are in an unstable state until it is added to the display list and goes through all the initialization stages (createChildren, commitProperties, measure, updateDisplayList, etc.).
If we run the test before this event, then depending on the speed and load of the computer on which the tests are running, we will receive the component in slightly different states. To prevent this, we must wait for the CREATION_COMPLETE event.
Suppose we want to create a button that would not only hide when setting visible = false, but would not affect the location of other components, i.e. includeInLayout = false.
Here, of course, you could includeInLayout = visible and set the set visible function in the function itself, but I want to show a more general case when there are many interconnected properties and all of them need to be updated in commitProperties.
In order to verify “by eye” that everything is working correctly for us, run the test application:
Now try to write the test itself:
package {
import flash.events.Event;
import flexunit.framework.Assert;
import mx.core.Application;
import mx.events.FlexEvent;
import org.flexunit.asserts.assertEquals;
import org.flexunit.async.Async;
public class HidingButtonAsyncTest {
public var myBtn:HidingButton;
// Этот код выполняется ПЕРЕД запуском каждого теста,
// помеченного метатегом [Test]
[Before( async )]
public function setUp():void {
myBtn = new HidingButton();
// Дождаться события CREATION_COMPLETE которое произойдет после вызова addChild
Async.proceedOnEvent(this, myBtn, FlexEvent.CREATION_COMPLETE, 1000);
Application.application.addChild(myBtn);
}
// Этот код выполняется ПОСЛЕ запуска каждого теста,
// помеченного метатегом [Test]
[After( async )]
public function tearDown():void {
Application.application.removeChild(myBtn);
myBtn = null;
}
[Test(async, description="тест значений по умолчанию")]
public function testDefaultState():void {
assertEquals(myBtn.visible, true);
assertEquals(myBtn.includeInLayout, true);
}
[Test(async, description="скрываем кнопку")]
public function testHideButton():void {
myBtn.addEventListener('includeInLayoutChanged',
Async.asyncHandler(this, handleVerifyProperty, 100, null, handleEventNeverOccurred), false, 0, true);
myBtn.visible = false;
// эта функция уникальная для каждого теста, вызывается при срабатывании нужного события
function handleVerifyProperty(event:Event, passThroughData:Object):void {
assertEquals(myBtn.includeInLayout, false);
}
}
// обработчик таймаута, общий для всех тестов
private function handleEventNeverOccurred(passThroughData:Object):void {
Assert.fail('Pending Event Never Occurred');
}
}
}
Nuances
1. As you can see from the code, I used the listener on the UIComponent 'includeInLayoutChanged' internal event to catch changes to the includeInLayout property. The FlexUnit documentation [1] recommends using FlexEvent.VALUE_COMMIT, but it is not always dispatched; you have to dispatch it yourself.
In the most general case, when it is difficult to isolate an event after which you can check the properties, you can test the component like any other asynchronous process: set a timer with a guaranteed long interval. Then our test will be rewritten like this:
[Test(async, description="проверка свойства по таймауту")]
public function testHideButtonWithTimer():void {
var timer:Timer = new Timer(50,1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE,
Async.asyncHandler(this, handleVerifyProperty, 100, {}, handleEventNeverOccurred), false, 0, true);
myBtn.visible = false;
timer.start();
function handleVerifyProperty(event:Event, passThroughData:Object):void {
assertEquals(myBtn.includeInLayout, false);
}
}
If you need to do a lot of such tests with a timer, then the initialization and zeroing of the timer must be carried out in the corresponding [Before] and [After] methods.
2. This test was written under Flex Sdk 3. *, if you are working with Flex 4. *, instead of Application.application.addChild (myBtn) you need to use FlexGlobals.topLevelApplication.addChild (myBtn) .
3. The disadvantage of adding test components directly to Application.application is that the component is visible in the test runner window, and also that the test runner styles can affect the styles of your component.
To avoid this, FlexUnit has a special UIImpersonator class that emulates adding a component to the display list.
UPD: To work from Intellij and ant, you need to remove flexunit-4.1.0-33-as3_3.5.0.12683.swc from the list of connected libraries. It conflicts with the same for flex.
List of references
1. FlexUnit dock: docs.flexunit.org/index.php?title=Main_Page
2. Creating and running unit tests from FlashBuilder: habrahabr.ru/blogs/Flash_Platform/89487
3. Refactoring reference book: Michael Feathers. Working Effectively with Legacy Code.