views:

746

answers:

2

I'm testing some event dispatch code in a Flex app, using FlexUnit's addAsync method for testing that events are dispatched. Great so far, I can ensure that at least one event was fired. However, I want to be a bit more detailed; I want to ensure that exactly the set of events I'm expecting are dispatched. Is there a useful test pattern (or, even, different test framework -- I'm flexible!) to accomplish this?

I tried this code, but it doesn't seem to get invoked the second time:

protected function expectResultPropertyChange(event: Event, numberOfEvents: int = 1): void {
    trace("Got event " + event + " on " + event.target + " with " + numberOfEvents + " traces left...");
    assertTrue(event.type == ResponseChangedEvent.RESPONSE_CHANGED);
    if (numberOfEvents > 1) {
        event.target.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, addAsync(expectResultPropertyChange, 1000, numberOfEvents - 1));
    }
}

public function testSomething(): void {
    requiredQuestion.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, addAsync(expectResultPropertyChange, 1000, 2));
    requiredQuestion.responseSelected("1", true);
    requiredQuestion.responseSelected("2", true);
}
+2  A: 

This is going to be a high level example of how a similar problem could be solved using a mocked out object of whatever it is that's doing the asynchronous call. Obviously i can't see your code so i can't give you a precise example.

So, as i said in the comment, you can mock out a dependency in a class to fake asynchronous calls so that they become synchronous. Take the below class

public class RequiredQuestion extends EventDispatcher
{
    private var someAsynchronousObject : IAsynchronousObject;

    public function RequiredQuestion(someAsynchronousObject : IAsynchronousObject = null)
    {
     someAsynchronousObject = someAsynchronousObject || new AsynchronousObject();
     someAsynchronousObject.addEventListener(Event.COMPLETE, asyncCallComplete);
    }

    public function responseSelected(id : String, flag : Boolean) : void
    {
        //Will asynchronously fire the Event.COMPLETE event
     someAsynchronousObject.startAsynchrounsCall(); 
    }

    protected function asyncCallComplete(event : Event) : void
    {
     dispatchEvent(new ResponseChangedEvent(ResponseChangedEvent.RESPONSE_CHANGED));
    }
}

So by default you are using the concrete class that you want to use unless someAsynchronousObjec is injected into the class via the constructor. AsycnhronousObject probably has it's own unit tests or it's in an external class so you don't really want, or need to be testing its functionality. What you can now do is create a mock object that implements IAsynchronousObject that can be used to fake its behavior. Using the ASMock framework the test could look something like this:

public function testSomething(): void 
{
    var mockIAsycnhronousObject :  IAsynchronousObject =
        IAsynchronousObject(mockRepository.createStrict( IAsynchronousObject));

    SetupResult.forEventDispatcher(mockIAsycnhronousObject);
    SetupResult.forCall(mockIAsycnhronousObject.startAsynchronousCall())
        .dispatchEvent(new Event(Event.COMPLETE)); // all calls to the startAsynchronousCall method and dispatch the complete event everytime it's called.

    mockRepository.replayAll();

    var requiredQuestion : RequiredQuestion = new RequiredQuestion(mockIAsycnhronousObject);

    var callCount : int = 0;
    requiredQuestion.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, function(event : ResponseChangedEvent)
    {
     callCount++;
    });

    requiredQuestion.responseSelected("1", true);
    requiredQuestion.responseSelected("2", true);

    assertEquals(2, callCount);

    mockRepository.verifyAll();
}

This is just one example of how mocking can help you unit tests. There's a whole wealth of info out there on mocking although it is still very new to ActionScript (released in December). ASMock is based on the .net Rhino mocks so searching for Rhino mocks should throw up a lot more results if you need help.

Definitely a different way of thinking but once you get into it you tend to wonder how you got by in unit testing without them.

James Hay
What if the event is dispatched directly? responseSelected doesn't trigger an asynchronous event on a composite object, it simply dispatched the RESPONSE_CHANGED event itself directly. I'm not seeing how this approach can be mocked using your method. Mind you, I'm fuzzy on the mock testing practice as-is, so I'm probably missing a simple solution here.
Chris R
+1  A: 

In response to the comment...

What if the event is dispatched directly? responseSelected doesn't trigger an asynchronous event on a composite object, it simply dispatched the RESPONSE_CHANGED event itself directly. I'm not seeing how this approach can be mocked using your method. Mind you, I'm fuzzy on the mock testing practice as-is, so I'm probably missing a simple solution here.

..in that case you don't need to use a mock or addAsync. Something like this will do:

public function testSomething(): void 
{
    var requiredQuestion : RequiredQuestion = new RequiredQuestion();

    var callCount : int = 0;
    requiredQuestion.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, function(event : ResponseChangedEvent)
    {
        callCount++;
    });

    requiredQuestion.responseSelected("1", true);
    requiredQuestion.responseSelected("2", true);

    assertEquals(2, callCount);
}
James Hay