Wednesday, 24 March 2010

Unit Testing Flex – Mocking « compactcode A Great Framework For Flex Mocking Called MOCKITO

26
Aug/091
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: flexunit, testing

Thursday, 18 March 2010

BetterDataGrid Improved

package
{
import flash.display.Sprite;
import flash.geom.Point;

import mx.collections.CursorBookmark;
import mx.controls.DataGrid;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.controls.listClasses.IListItemRenderer;
import mx.core.mx_internal;
use namespace mx_internal;


/**
 *  A DataGrid subclass that has faster horizontal scrolling
 */
public class BetterDataGrid extends DataGrid
{
    public function BetterDataGrid()
    {
        super();
    }

    /**
     *  remember the number of columns in case it changes
     */
    private var lastNumberOfColumns:int;

    /**
     *  a flag as to whether we can use the optimized scrolling
     */
    private var canUseScrollH:Boolean;

    /**
     *  when the horizontal scrollbar is changed it will eventually set horizontalScrollPosition
     *  This value can be set programmatically as well.
     */
    override public function set horizontalScrollPosition(value:Number):void
    {
        // remember the setting of this flag.  We will tweak it in order to keep DataGrid from
        // doing its default horizontal scroll which essentially refreshes every renderer
        var lastItemsSizeChanged:Boolean = itemsSizeChanged;

        // remember the current number of visible columns.  This can get changed by DataGrid
        // as it recomputes the visible columns when horizontally scrolled.
        lastNumberOfColumns = visibleColumns.length;

        // reset the flag for whether we use our new technique
        canUseScrollH = false;

        // call the base class.  If we can use our technique we'll trip that flag
        super.horizontalScrollPosition = value;

        // if the flag got tripped run our new technique
        if (canUseScrollH)
        {
            scrollLeftOrRight();
            configureScrollBars();
        }

        // reset the flag
        itemsSizeChanged = lastItemsSizeChanged;

    }

    // remember the parameters to scrollHorizontally to be used in our new technique
    private var pos:int;
    private var deltaPos:int;
    private var scrollUp:Boolean;

    public function get attrColumnIndex():int {

        return _attrColumnIndex;
    }

    public function set attrColumnIndex(o:int):void {
        _attrColumnIndex = o;
    }

    protected var _attrColumnIndex:int = null;


    // override this method.  If it gets called that means we can use the new technique
    override protected function scrollHorizontally(pos:int, deltaPos:int, scrollUp:Boolean):void
    {
        // just remember the args for later;
        this.pos = pos;
        this.deltaPos = deltaPos;
        this.scrollUp = scrollUp;
        if (deltaPos < visibleColumns.length)
        {
            canUseScrollH = true;

            // need this to prevent DG from asking for a full refresh
            itemsSizeChanged = true;
        }
    }

    /**
     *  The new technique does roughly what we do vertically.  We shift the renderers on screen and in the
     *  listItems array and only make the new renderers.
     *  Because we can't get internal access to the header, we fully refresh it, but that's only one row
     *  of renderers.  There's significant gains to be made by not fully refreshing the every row of columns
     *
     *  Key thing to note here is that visibleColumns has been updated, but the renderer array has not
     *  That's why we don't do this in scrollHorizontally as the visibleColumns hasn't been updated yet
     *  But because of that, sometimes we have to measure old renderers, and sometimes we measure the columns
     */
    private function scrollLeftOrRight():void
    {
        // trace("scrollHorizontally " + pos);
        var i:int;
        var j:int;

        var numCols:int;
        var uid:String;

        var curX:Number;

        var rowCount:int = rowInfo.length;
        var columnCount:int = listItems[0].length;
        var cursorPos:CursorBookmark;

        var moveBlockDistance:Number = 0;

        var c:DataGridColumn;
        var item:IListItemRenderer;
        var itemSize:Point;
        var data:Object;

        var xx:Number;
        var yy:Number;

        if (scrollUp) // actually, rows move left
        {
            // determine how many columns we're discarding
            var discardCols:int = deltaPos;

            // measure how far we have to move by measuring the width of the columns we
            // are discarding

            moveBlockDistance = sumColumnWidths(discardCols, true);
            // trace("moveBlockDistance = " + moveBlockDistance);

            //  shift rows leftward and toss the ones going away
            for (i = 0; i < rowCount; i++)
            {
                numCols = listItems[i].length;

                 if(numCols == 0) //empty row
                   continue;

                // move the positions of the row, the item renderers for the row,
                // and the indicators for the row
                moveRowHorizontally(i, discardCols, -moveBlockDistance, numCols);
                // move the renderers within the array of rows
                shiftColumns(i, discardCols, numCols);
                truncateRowArray(i);
            }

            // generate replacement columns
            cursorPos = iterator.bookmark;

            var firstNewColumn:int = lastNumberOfColumns - deltaPos;
            curX = listItems[0][firstNewColumn - 1].x + listItems[0][firstNewColumn - 1].width;


            for (i = 0; i < rowCount; i++)
            {
                if(iterator == null || iterator.afterLast || !iteratorValid)
                   continue;
                data = iterator.current;
                iterator.moveNext();
                uid = itemToUID(data);

                xx = curX;
                yy = rowInfo[i].y;
                for (j = firstNewColumn; j < visibleColumns.length; j++)
                {
                    c = visibleColumns[j];
                    item = setupColumnItemRenderer(c, listContent, i, j, data, uid);
                    //if(!item) return;
                    itemSize = layoutColumnItemRenderer(c, item, xx, yy);
                    xx += itemSize.x;
                }
                // toss excess columns
                while (listItems[i].length > visibleColumns.length)
                {
                    addToFreeItemRenderers(listItems[i].pop());
                }
            }

            iterator.seek(cursorPos, 0);
        }
        else
        {
            numCols = listItems[0].length;

            if(deltaPos > visibleColumns.length)
             deltaPos = visibleColumns.length;

            moveBlockDistance = sumColumnWidths(deltaPos, false);

            // shift the renderers and slots in array
            for (i = 0; i < rowCount; i++)
            {
                numCols = listItems[i].length;
                 if(numCols == 0)
                    continue;

                moveRowHorizontally(i, 0, moveBlockDistance, numCols);
                // we add placeholders at the front for new renderers
                addColumnPlaceHolders(i, deltaPos);

            }

            cursorPos = iterator.bookmark;

            for (i = 0; i < rowCount; i++)
            {
                data = iterator.current;
                iterator.moveNext();
                uid = itemToUID(data);

                xx = 0;
                yy = rowInfo[i].y;
                for (j = 0; j < deltaPos; j++)
                {
                    c = visibleColumns[j];
                    item = setupColumnItemRenderer(c, listContent, i, j, data, uid);
                    itemSize = layoutColumnItemRenderer(c, item, xx, yy);
                    xx += itemSize.x;
                }
                // toss excess columns
                while (listItems[i].length > visibleColumns.length)
                {
                    addToFreeItemRenderers(listItems[i].pop());
                }
            }

            iterator.seek(cursorPos, 0);
        }

        // force update the header
        header.headerItemsChanged = true;
        header.visibleColumns = visibleColumns;
        header.invalidateDisplayList();
        header.validateNow();

        // draw column lines and backgrounds
        drawLinesAndColumnBackgrounds();
    }

    /* override protected function addToFreeItemRenderers(item:IListItemRenderer):void
    {
        if (item) super.addToFreeItemRenderers(item);
    } */

    // if moving left, add up old renderers
    // if moving right, add up new columns
    private function sumColumnWidths(num:int, left:Boolean):Number
    {
        var i:int;
        var value:Number = 0;
        if (left)
        {
            for (i = 0; i < num; i++)
            {
                value += listItems[0][i].width;
            }
        }
        else
            for (i = 0; i < num; i++)
            {
                value += visibleColumns[i].width;
            }

        return value;
    }

    // shift position of renderers on screen
    private function moveRowHorizontally(rowIndex:int, start:int, distance:Number, end:int):void
    {
        for (;start < end; start++)
        if(listItems[rowIndex][start]){
            listItems[rowIndex][start].x += distance;
        }
    }

    // shift renderer assignments in listItems array
    private function shiftColumns(rowIndex:int, shift:int, numCols:int):void
    {
        var item:IListItemRenderer;
        var uid:String = itemToUID(listItems[rowIndex][0].data);
        for (var i:int = 0; i < shift; i++)
        {
            item = listItems[rowIndex].shift();
            if(item){
                addToFreeItemRenderers(item);
            }
        }
        //rebuild the listContent.visibleData map entry
        listContent.visibleData[uid] = listItems[rowIndex][0];
    }

    // add places in front of row for new columns
    private function addColumnPlaceHolders(rowIndex:int, count:int):void
    {
        for (var i:int = 0; i < count; i++)
        {
            listItems[rowIndex].unshift(null);
        }
    }

    // remove excess columns
    private function truncateRowArray(rowIndex:int):void
    {
        while (listItems[rowIndex].length > visibleColumns.length)
        {
            var item:IListItemRenderer;
            {
                item = listItems[rowIndex].pop();
                addToFreeItemRenderers(item);
            }
        }
    }

    override protected function drawVerticalLine(s:Sprite, colIndex:int, color:uint, x:Number):void
    {
        if(!attrColumnIndex){
            super.drawVerticalLine(s:Sprite, colIndex:int, color:uint, x:Number);
            return;
        }
        if (colIndex == attrColumnIndex-pos)
        {
            var colour:uint = 0xFF0000;
            super.drawVerticalLine(s,colIndex,colour,x);
            return;
        }
        super.drawVerticalLine(s,colIndex,color,x);
    }

 }
}

Posted via email from fasanya's posterous

Thursday, 11 March 2010

Mockito

Mockito is a framework to help you create stubs and mocks quickly if you do Unit Testing.

Check the link out and the tutorial. 

http://bitbucket.org/loomis/mockito-flex/wiki/Home

Tutorial for version 1.0

This tutorial is unit testing framework agnostic. For examples on how to use it with specific unit testing frameworks please check the integrations section.

Let's

Before you will be able to create your mock objects you need to tell Mockito to prepare for it. You do it by calling:

1 
mockito
.prepareClasses([...]) ;

and providing all the classes to mock in given test case. Since the bytecode generation is an asynchronous process it is recommended to use one of the integration test cases as they address this issue. You may find more answers inAsmock documentation.

After preparing classes you can create mock objects by invoking:

1 
var dependency:Dependency = Dependency(mockito.mock(Dependency)); 

Then setup the System Under Test (SUT)

1 
var sut:Sut = new Sut(dependency); 

And execute tested code.

1 
sut.testedFunction(10); 

Given the testedFunction looks like this:

1 2 3 4 
function testedFunction(input:int):void  {  dependencyResult = dependency.someOperation(input);  } 

Notice that there is no 'unexpected call' exception.

Instead you are free to choose what you want to verify:

1 
verify().that(dependency.someOperation(input)); 

The full test would look like this:

...  mockito.prepareClasses([Dependency]);  ...  var dependency:Dependency = Dependency(mockito.mock(Dependency));  // given  var sut:Sut = new Sut(dependency);  // when  sut.testedFunction(10);  // then  mockito.verify().that(dependency.someOperation(10)); 

As you can see, verification happens where assertions go. Not before the tested code. Important note is that verify() is equivalent to verify(times(1)).

If you need to stub dependency, you define it upfront.

1 
mockito.given(dependency.someOperation(10)).willReturn(1); 

When used in test it would look like this:

...  mockito.prepareClasses([Dependency]);  ...  var dependency:Dependency = Dependency(mockito.mock(Dependency));  // given  var sut:Sut = new Sut(dependency);  mockito.given(dependency.someOperation(10)).willReturn(1);  // when  sut.testedFunction(10);  // then  assertEquals(1, sut.dependencyResult); 

It may be useful to verify or define stubbing with various arguments at a time or have ability of matching the specific cases. For that purpose Mockito provides Matchers. Below example verifies any arguments passed to the function:

1 
mockito.verify().that(dependency.someOperation(mockito.any())); 

Similar you can do with stubbing:

1 
mockito.given(dependency.someOperation(any())).willReturn(1); 

As you can see you can either use explicit values or matchers when defining stubs or verifying. But you cannot mix it within single stub definition or verification. So for instance:

1 
mockito.verify().that(progress.update(10, mockito.any())) 

is invalid. Mockito will not be able to figure out which argument type to match against the any() matcher. You may want to verify multiple executions of a method at time. It's easy. Just tell how to verify:

1 
mockito.verify(mockito.times(3)).that(dependency.someOperation(mockito.any())); 

Sometimes you may want to make sure method has not been called. Do it by verifying:

Posted via email from fasanya's posterous

Ever wondered how to see the entire stack trace how you got to a particular method? (without trawling through eclipse and tons fo breakpoints)

trace(new Error().getStackTrace())

Handy …. J

The contents of this message and any attachments to it are confidential and may be legally privileged. If you have received this message in error you should delete it from your system immediately and advise the sender. dunnhumby may monitor and record all emails. The views expressed in this email are those of the sender and not those of dunnhumby.

Posted via email from fasanya's posterous

Nice Css list for flex.

http://www.loscavio.com/downloads/blog/flex3_css_list/flex3_css_list.htm

The contents of this message and any attachments to it are confidential and may be legally privileged. If you have received this message in error you should delete it from your system immediately and advise the sender. dunnhumby may monitor and record all emails. The views expressed in this email are those of the sender and not those of dunnhumby.

Posted via email from fasanya's posterous