What’s the point of all this mockery?

I’ve jumped in with both feet, without pausing to explain why I’m doing it – so, where do mock objects fit into my daily testing behaviours?

  • when the system under test relies on an expensive resource
  • when the system under test relies on a live resource
  • when the system under test relies on a resource that doesn’t yet exist
  • when I want to ensure that the system under test uses the resource correctly

I had to build a couple of client applications recently (twitter and flickr, since you ask – talk about re-inventing the wheel …), which interacted with what I consider expensive resources – firstly, I was contacting the remote flickr servers and awaiting their response each time I tested; secondly, the twitter API puts limits on how often it’s called, which means that I could run out of calls pretty quickly if every test made at least one call, and all tests were run each time I compiled the movie.

A couple of years ago, we built a googlemaps-stylee flash app for a certain weekend-break company to allow users to choose chalets (/lodges/huts/accommodation units – bizarrely ‘accommodation units’ was the official term) online. The units’ availability was updated in real time, so the app had to query the server for the latest data. The only problem with this was that the server application was still under design – it didn’t yet exist.

I’ve also worked with database developers who haven’t yet finalised their DB structure, and who keep dropping and re-building the database, which means deleting all the data that my test scripts rely on.

You only need to experience difficulties such as these a couple of times to realise that it’s imperative to have control of the remote/live/non-existent/under-development resource in order for the tests to be of any use.

The good news is that – in most cases – it’s possible to define an API for communication with the resource even before it exists, and that’s what makes mock objects so useful – they can honour the API, thus enabling the code we build against the tests to work against the resource when it’s ready.

Strikes me an example might be useful. Twitter suit you?

I have a TwitterView object, which takes a TwitterProxy which in turn communicates with the Twitter servers. I know it’s terrible – aren’t these chaps supposed to be cleanly separated? The TwitterView receives an event when the proxy has done all its loading, and then can populate the relevant TextFields. Easy, innit.

  1. public class TwitterView extends Sprite {
  2.        
  3.         private var title:TextField;
  4.         private var tweets:TextField;
  5.         private var proxy:ITwitterProxy;
  6.        
  7.         public function TwitterView(proxy:ITwitterProxy = null) {
  8.             this.proxy = proxy;
  9.             initView();
  10.             setTitle("Hello World!");
  11.         }
  12.        
  13.         public function getTitle():String {
  14.             return title.text;
  15.         }
  16.  
  17.         public function setTitle(title:String):void {
  18.             this.title.text = title;
  19.         }
  20.        
  21.         public function setTweets(tweets:Array):void {
  22.             this.tweets.htmlText = tweets.join("\n\n");
  23.         }
  24.  
  25.         override public function toString():String {
  26.             return "[TwitterView]";
  27.         }
  28.  
  29.         public function init():void {
  30.             if(proxy == null) {
  31.                 proxy = new TwitterProxy();
  32.             }
  33.             proxy.addEventListener(TwitterLoadEvent.TWITTER_LOADED, onTwitterLoaded);
  34.             proxy.init();
  35.         }
  36.  
  37.         private function initView():void {
  38.             title = new TextField();
  39.             title.autoSize = TextFieldAutoSize.LEFT;
  40.             addChild(title);
  41.            
  42.             tweets = new TextField;
  43.             tweets.width = 500;
  44.             tweets.autoSize = TextFieldAutoSize.LEFT;
  45.             tweets.wordWrap = true;
  46.             tweets.y = title.y + title.height + 10;
  47.             addChild(tweets);
  48.         }
  49.        
  50.         public function onTwitterLoaded(event:TwitterLoadEvent):void {
  51.             setTitle(event.proxy.getTitle());
  52.             setTweets(event.proxy.getTweets());
  53.         }
  54.     }

In the onTwitterLoaded function, you can see the two most important calls that we need to make on the TwitterProxy: getTitle() and getTweets(). I put these in the ITwitterProxy interface.

  1. public interface ITwitterProxy extends IEventDispatcher {
  2.        
  3.         function getTweets():Array;
  4.         function getTitle():String;
  5.        
  6.         function init():void;
  7.     }

So now we have a fairly basic view, which expects its data from the proxy.

Let’s see it in action, courtesy of TwitterApp. Yep, not much happening:

  1. public class TwitterApp extends Sprite {
  2.  
  3.         public function TwitterApp() {
  4.             init();
  5.         }
  6.        
  7.         private function init():void {
  8.             var view:TwitterView = new TwitterView();
  9.             addChild(view);
  10.             view.init();
  11.         }
  12.     }

We want to practise test-driven development – write a failing test > code until it passes > write another test > refactor – so we want to write a unit test that will check the view’s title and tweets, but (a) we can’t rely on the live twitter data as it will change over time, and (b) we don’t want to run up against the limit of API calls that we can make. Time for a mock object, methinks.

Firstly, I’m using ASUnit rather than FlexUnit, so if you’re following along, you’ll need to grab that and the ASUnitMockitoTestCase bridging class that I published one or two articles ago. Let’s start with a skeleton testcase:

  1. public class TwitterTest extends ASUnitMockitoTestCase {
  2.  
  3.         public function TwitterTest(testMethod:String = null) {
  4.             super([], testMethod);
  5.         }
  6.  
  7.         public function testBasic():void {
  8.             assertTrue("failing test", 1 + 1 == 5);
  9.         }
  10.  
  11. }

Notice that we have a basic test that will show up as a fail in ASUnit, so that we can be sure that we’ve hooked everything together successfully. As soon as you see the fail, you can delete it. Note also that the call to super() starts with an empty Array – in time we’ll populate with the classes that we wish to set up as mocks.

Now, let’s pause for a think about what we want to test here – the View relies on the proxy for its data, so we want to check that the view gets its data successfully from the proxy without hitting the live Twitter servers. Oh, and the proxy doesn’t exist yet, just an interface.

I’m going to start really slowly here, forgive me – adding the assert that checks the view’s title.

  1. public class TwitterTest extends ASUnitMockitoTestCase {
  2.  
  3.         public function TwitterTest(testMethod:String = null) {
  4.             super([], testMethod);
  5.         }
  6.        
  7.         public function testViewGetTitle():void {
  8.             assertEquals("Twitter updates", view.getTitle());
  9.         }
  10. }

Immediate compile error, since we haven’t declared view, so let’s add another line:

  1. var view:TwitterView = new TwitterView();
  2. assertEquals("Twitter updates", view.getTitle());

Still no success, because the TwitterView cannot be instantiated without a ITwitterProxy, which we don’t have, so let’s mock that (note that I’ve also added ITwitterProxy to the super() in the TwitterTest() constructor).

  1. public class TwitterTest extends ASUnitMockitoTestCase {
  2.  
  3.         public function TwitterTest(testMethod:String = null) {
  4.             super([ITwitterProxy], testMethod);
  5.         }
  6.        
  7.         public function testViewGetTitle():void {
  8.             var mockProxy:ITwitterProxy = mock(ITwitterProxy) as ITwitterProxy;
  9.             var view:TwitterView = new TwitterView(mockProxy);
  10.             view.init();
  11.             assertEquals("Twitter updates", view.getTitle());
  12.         }
  13. }

Huzzah – the movie compiles and we get our failing test :) For the record, mock() takes the interface and builds an object based on it that will record all calls made on its methods – it’s very, very clever.

First thing, let’s set the mockProxy up to give us the title string that we expect – add this line above the creation of the view:

  1. given(mockProxy.getTitle()).willReturn("Twitter updates");

This beautiful line of code says: if someone calls getTitle() on the mockProxy, the mockProxy will return “Twitter updates”. Isn’t that cool? Of course, that doesn’t help pass the test just yet.

The view is expecting to receive a TwitterLoadEvent from the proxy, triggering onTwitterLoaded(), so our mock object needs to have IEventDispatcher functionality. However, because the mockObject does so much weird stuff behind the scenes that I don’t have (and don’t want to have) a clue about, I’m going to attack this another way.

  1. given(mockProxy.addEventListener(any(), any())).will(fireImmediateLoadEvent);

When mockProxy.addEventListener() is called, it will fire an immediate load event – this is mocking the request/response communication with the Twitter server. So what’s fireImmediateLoadEvent?

It’s a GenericAnswer object, which just holds a function that will be called when addEventListener() is called; in this case I want it to be like this:

  1. var fireImmediateLoadEvent:Answer = new GenericAnswer(
  2.     function():void {
  3.         // record the event and eventHandler somehow
  4.         // then immediately fire the event with the required data
  5.     }
  6. );

Of course, in the real world, the proxy will be doing this as part of its work, but because we’re using a mock proxy, we have to work around it a bit. I’ve come up with this:

  1. var d:EventDispatcher = new EventDispatcher({} as IEventDispatcher);
  2.  
  3. var fireImmediateLoadEvent:Answer = new GenericAnswer(
  4.     function():void {
  5.         // record the event and eventHandler somehow
  6.         d.addEventListener(TwitterLoadEvent.TWITTER_LOADED, view.onTwitterLoaded);
  7.         // then immediately fire the event with the required data
  8.         d.dispatchEvent(new TwitterLoadEvent(mockProxy));
  9.     }
  10. );

So now the test looks like this:

  1.     public function testViewGetTitle():void {
  2.         // create an EventDispatcher that can be used as the dispatching functionality for
  3.         // the mock ITwitterClient
  4.         var d:EventDispatcher = new EventDispatcher({} as IEventDispatcher);
  5.        
  6.         var mockProxy:ITwitterProxy = mock(ITwitterProxy) as ITwitterProxy;
  7.         given(mockProxy.getTitle()).willReturn("Twitter updates");
  8.         given(mockProxy.getTweets()).willReturn(["Tweet#1", "Tweet#2", "Tweet#3"]);
  9.  
  10.         var view:TwitterView = new TwitterView(mockProxy);
  11.         var fireImmediateLoadEvent:Answer = new GenericAnswer(
  12.             function():void {
  13.                 // add the listening class
  14.                 d.addEventListener(TwitterLoadEvent.TWITTER_LOADED, view.onTwitterLoaded);
  15.                 // then immediately fire the event – this mocks the XML-loading that really occurs
  16.                 d.dispatchEvent(new TwitterLoadEvent(mockProxy));
  17.             }
  18.         );
  19.         given(mockProxy.addEventListener(any(), any())).will(fireImmediateLoadEvent);
  20.         view.init();
  21.            
  22.         assertEquals("Twitter updates", view.getTitle());
  23.     }

For completeness, here’s TwitterLoadEvent:

  1. public class TwitterLoadEvent extends Event {
  2.        
  3.     public static const TWITTER_LOADED:String = "onTwitterLoaded";
  4.        
  5.     private var _proxy:ITwitterProxy;
  6.  
  7.     public function TwitterLoadEvent(proxy:ITwitterProxy) {
  8.         super(TWITTER_LOADED, false, false);
  9.         _proxy = proxy;
  10.     }
  11.        
  12.     public function get proxy():ITwitterProxy {
  13.         return _proxy;
  14.     }
  15. }