Tuesday, November 30, 2010

Unit Test Cases - Mocking and Stubbing

When it comes to unit testing, what's the biggest issue you face? I generally get stuck with the dependencies among different classes, stubbing the actual environment. I am a Flash - Flex programmer but feel that the issue applies to any language you code in.

I am not too deeply familiar with the jargon of Unit Testing but I always try and keep implementing new things that help write better code. So, if you are a master of Unit Testing, the post is not for you. It is more for the people like me getting started with this. Do not expect it to be a tutorial on Unit Testing, but I would share my first experience with mocking. I also do hope that audience have a basic understanding of writing test cases with Flex Unit 4.

I wanted to test out the function below:

public function handleTaskComplete(evt:TaskEvent):void
{
   this.data = evt.target.data.toString().toLowerCase();
}

The problem is that the TaskEvent class does not allow me to specify the target property so I cannot simply create an instance of TaskEvent and pass it on. Similarly the Object returned by TaskEvent does not allow to set the data property. Mocking comes in handy here.

I can create a mock class for TaskEvent and also mock it's target method to return what I desire. There are loads of free Flex-AS3 mocking libraries but I found mockolate easiest to comprehend ;) hence I used it. Open source library, available at "http://mockolate.org/".

It took me a litte time to get all the pieces together. Basically, the mockolate source uses these libraries as well:
  • asx.swc
  • flexunit-core-flex-4.1.0-beta3.x-sdky.y.y.y.swc
  • FLoxy.swc
  • hamcrest-as3-1.1.0.swc
I create a new library project and combined all of these to one SWC. If you need it, give me a shout.

Once I had the SWC ready and included in my project, I proceeded to code.


[Test(async, timeout=30000)]
public function testMenuBarLoad():void
{
   Async.handleEvent(this,
   prepare(TaskEvent, XmlLoaderTask),
   Event.COMPLETE, startActualTest, 10000);
}

As you'd notice, the function above is my test function. I've made it an asynchronous function with some timeout. Before, being able to mock or stub any classes, we have to prepare them. Once the classes are prepared, "complete" event is dispatched. I can specify any number of classes to be prepared at once.

Once my classes were ready for mocking, startActualTest event was dispatched.

private function startActualTest (event:Event, data:Object):void
{
   var data:XML = getTestData();
   var taskEvent:TaskEvent = nice(TaskEvent, null, ["A"]);
   var xmlTask:XmlLoaderTask = nice(XmlLoaderTask, null, ["A"]);

   mock(taskEvent).getter("target").returns(xmlTask);
   mock(xmlTask).getter("data").returns(data);

   myComponent.handleTaskComplete(taskEvent);
   assetEquals(data, myComponent.data)
}

Let me go through the code in the function above.

Firstly, I create a nice mock object for the TaskEvent and XMLLoaderTask with the constructor arguments in the array. Mockolate allows 2 types of mock, nice and strict. Read documentation for details.

Secondly, we mock the getter "target" in taskEvent mock to return our mock object of xmlTask.

Thirdly, we mock the getter of xmlTask to return the desired data. Finally, we invoke component's handleTaskComplete method with the mocked taskEvent . In the last line, we add an assert to test whether data has been successfully set.

Mockolate provides us with provisions to test methods. It even allows us to define that if a method is called with a particular argument or a particular argument type, what should be returned. For example:

stub(flavour).method("combine").args(otherFlavour).returns(combinedFlavour);
We can also verify whether the functions mocked by us have been invoked or nor. This is just one of many cool things you can do with mockolate. I found mocking using mockolate to be of great help in creating a stub environment for unit testing.

Hope this post be of some help to you. Write to me at pulkit.gupta@gmail.com for any feedback.

I faced some issues getting a build ready for unit testing using ANT, so I would probably add that in my next post.