⬆️ ⬇️

Features testing Flex UI components using FlexUnit 4

At work, somehow it was necessary to add functionality to one self-signed flex component. It was important not to break the already existing behavior, because during its existence, the component has been used in several applications and acquired by successors.



The standard approach to solving such problems is to start by writing unit tests covering the current behavior of a component, at the same time clarifying for itself the features of its structure.

Only after that you can begin step-by-step refactoring and extending the functionality, constantly running tests to see if anything has broken as a result of the changes. [3]



The task, however, is complicated by the fact that the 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 the heirs of UIComponent, we must take into account that most of the component properties are in an unstable state until it is added to the display list and passes through all initialization stages (createChildren, commitProperties, measure, updateDisplayList, etc.).



If we run a test before this event, then depending on the speed and load of the computer on which the tests are run, we will receive the component in slightly different states. To avoid 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 also did not affect the location of other components, that is, includeInLayout = false.



<?xml version="1.0"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ override public function set visible(value:Boolean):void { super.visible = value; invalidateProperties(); } override protected function commitProperties():void { super.commitProperties(); includeInLayout = visible; //  ,     VALUE_COMMIT //dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); } ]]></mx:Script> </mx:Button> 


Here, of course, it was possible to includeInLayout = visible in the set visible function itself, but I would like to show a more general case when there are many interrelated properties and all of them need to be updated in commitProperties.



In order to “see by eye” that everything works correctly for us, we will launch a test application:

 <?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*"> <local:HidingButton id="hidingBtn" label="I'll hide"/> <mx:Button label="try me" click="hidingBtn.visible = ! hidingBtn.visible"/> </mx:Application> 




Now let's 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 can be seen from the code, I used a listener on the UIComponent internal event 'includeInLayoutChanged' to catch changes to the includeInLayout property. The documentation on FlexUnit [1] recommends using FlexEvent.VALUE_COMMIT, but it does not always dispute, you have to dispute it yourself.



In the most general case, when it is difficult to select any event, after which you can check the properties, you can test the component like any other asynchronous process: set a timer with a guaranteed large interval. Then our test will be rewritten as follows:

 [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 reset of the timer should be brought into the appropriate methods [Before] and [After].



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. Example how to use UIImpersonator, see below in the comments.



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.



Bibliography


1. Doc on FlexUnit: docs.flexunit.org/index.php?title=Main_Page

2. Creating and launching unit tests from FlashBuilder: habrahabr.ru/blogs/Flash_Platform/89487

3. Handbook of refactoring: Michael Feathers. Working Effectively with Legacy Code.

Source: https://habr.com/ru/post/132255/



All Articles