如何使用FlexUnit 4和mockolate在模拟对象上模拟AsyncResponder和结果处理函数

时间:2012-03-07 04:07:46

标签: flex flexunit mockolate

我正在尝试为我在Flex 4.5.1中编写的类编写一些单元测试,分别使用FlexUnit 4和Mockolate进行测试和模拟框架。我正在使用as3-signals进行自定义活动。

我正在编写和测试的功能是围绕QueryTaskArcGIS API for Flex类的包装类(QueryQueue)。这使我能够轻松排队多个查询任务以便执行。我的包装器,QueryQueue将在处理完所有查询响应后调度completed事件。

界面非常简单。

public interface IQueryQueue
{
    function get inProgress():Boolean;
    function get count():int;

    function get completed():ISignal;
    function get canceled():ISignal;

    function add(query:Query, url:String, token:Object = null):void; 
    function cancel():void;
    function execute():void;
}

以下是一个示例用法:

public function exampleUsage():void
{
    var queryQueue:IQueryQueue = new QueryQueue(new QueryTaskFactory());
    queryQueue.completed.add(onCompleted);
    queryQueue.canceled.add(onCanceled);

    var query1:Query = new Query();
    var query2:Query = new Query();
    // set query parameters

    queryQueue.add(query1, url1);
    queryQueue.add(query2, url2);

    queryQueue.execute();
}

public function onCompleted(sender:Object, event:QueryQueueCompletedEventArgs)
{
    // do stuff with the the processed results
}

public function onCanceled(sender:Object, event:QueryQueueCanceledEventArgs)
{
    // handle the canceled event
}

对于我的测试,我目前正在模拟QueryTaskFactory和QueryTask对象。简单的测试,例如确保将查询相对简单地添加到队列中。

[Test(Description="Tests adding valid QueryTasks to the QueryQueue.")]
public function addsQuerys():void
{
    var queryTaskFactory:QueryTaskFactory = nice(QueryTaskFactory);
    var queryQueue:IQueryQueue = new QueryQueue(queryTaskFactory);
    assertThat(queryQueue.inProgress, isFalse());
    assertThat(queryQueue.count, equalTo(0));

    var query1:Query = new Query();
    queryQueue.add(query1, "http://gisinc.com");
    assertThat(queryQueue.inProgress, isFalse());
    assertThat(queryQueue.count, equalTo(1));

    var query2:Query = new Query();
    queryQueue.add(query2, "http://gisinc.com");
    assertThat(queryQueue.inProgress, isFalse());
    assertThat(queryQueue.count, equalTo(2));

    var query3:Query = new Query();
    queryQueue.add(query3, "http://gisinc.com");
    assertThat(queryQueue.inProgress, isFalse());
    assertThat(queryQueue.count, equalTo(3));
}

但是,我希望能够测试execute方法。此方法应执行添加到队列的所有查询。处理完所有查询结果后,将调度completed事件。测试应确保:

    每次查询只会调用
  1. execute一次
  2. inProgress = true,但结果尚未处理
  3. 处理结果时
  4. inProgress = false
  5. completed在处理结果时调度
  6. 永远不会调用
  7. canceled(对于有效查询)
  8. 队列内完成的处理正确处理并打包查询结果
  9. 到目前为止,我可以为第1项到第5项编写测试,这在很大程度上要归功于weltraumpirat提供的答案。我的执行测试现在看起来像这样。

    [Test(async, description="Tests that all queryies in the queue are executed and the completed signal is fired")]
    public function executesAllQueriesInQueue():void
    {
        // Setup test objects and mocks
        var query:Query = new Query();
        var mockedQueryTask:QueryTask = nice(QueryTask);
        var mockedQueryTaskFactory:QueryTaskFactory = nice(QueryTaskFactory);
    
        // Setup expectations
        expect(mockedQueryTaskFactory.createQueryTask("http://test.com")).returns(mockedQueryTask);
        expect(mockedQueryTask.execute(query, null)).once();
    
        // Setup handlers for expected and not expected signals (events)
        var queryQueue:IQueryQueue = new QueryQueue(mockedQueryTaskFactory);
        handleSignal(this, queryQueue.completed, verifyOnCompleted, 500, null);
        registerFailureSignal(this, queryQueue.canceled);
    
        // Do it
        queryQueue.add(query, "http://test.com");
        queryQueue.execute();
    
        // Test that things went according to plan
        assertThat(queryQueue.inProgress, isTrue());
        verify(mockedQueryTask);
        verify(mockedQueryTaskFactory);
    
        function verifyOnCompleted(event:SignalAsyncEvent, passThroughData:Object):void
        {
            assertThat(queryQueue.inProgress, isFalse());
        }
    }
    

    QueryQueue.execute方法如下所示。

    public function execute():void
    {
        _inProgress = true;
    
        for each(var queryObject:QueryObject in _queryTasks)
        {
            var queryTask:QueryTask = _queryTaskFactory.createQueryTask(queryObject.url);
            var asyncToken:AsyncToken = queryTask.execute(queryObject.query);
    
            var asyncResponder:AsyncResponder = new AsyncResponder(queryTaskResultHandler, queryTaskFaultHandler, queryObject.token);
            asyncToken.addResponder(asyncResponder);
        }
    }
    
    private function queryTaskResultHandler(result:Object, token:Object = null):void
    {
        // For each result collect the data and stuff it into a result collection
        // to be sent via the completed signal when all querytask responses
        // have been processed.
    }
    
    private function queryTaskFaultHandler(error:FaultEvent, token:Object = null):void
    {
        // For each error collect the error and stuff it into an error collection
        // to be sent via the completed signal when all querytask responses
        // have been processed.
    }
    

    对于上面的测试#6,我希望能够测试queryTaskResultHandlerqueryTaskFaultHandler中返回的数据是否得到了正确处理。

    也就是说,在所有查询响应都返回之前,我不会调度completed事件,包括成功和失败的结果。

    为了测试这个过程,我认为我需要模拟每个模拟查询任务的结果和故障处理程序中返回的数据。

    那么,如何使用FlexUnit和mockolate模拟传递给通过AsyncResponder创建的结果处理程序的数据。

1 个答案:

答案 0 :(得分:1)

您可以使用mockolate模拟任何对象或接口。根据我的经验,最好设置一个规则并像这样嘲笑:

[Rule]
public var rule : MockolateRule = new MockolateRule();

[Mock]
public var task : QueryTask;

请注意,必须实例化规则,而不是模拟对象。

然后您可以指定您的期望:

[Test]
public function myTest () : void {
    mock( task ).method( "execute" ); // expects that the execute method be called
}

你可以期待很多东西,比如参数:

    var responder:AsyncResponder = new AsyncResponder(resultHandler, faultHandler);
    mock( task ).method( "execute" ).args( responder ); // expects a specific argument

或者让对象返回特定值:

    mock( queue ).method( "execute" ).returns( myReturnValue ); // actually returns the value(!)

从模拟对象发送事件就像在其上调用dispatchEvent一样简单 - 因为您正在模拟原始类,它会继承其所有功能,包括EventDispatcher

现在针对您的特殊情况,我最好模拟所有三个外部依赖项的使用:QueryQueryTaskAsyncResponder ,因为它不是你测试的功能,而是你Queue的功能。

由于您在队列中创建这些对象,因此很难模拟它们。事实上,你不应该在任何类中直接创建任何东西,除非没有外部依赖!相反,为您必须创建的每个对象传入一个工厂(您可能希望使用依赖注入框架) - 然后您可以在测试用例中模拟该工厂,并让它根据需要返回模拟对象:

public class QueryFactory {
    public function createQuery (...args:*) : Query {
       var query:Query = new Query();
       (...) // use args array to configure query
       return query;
    }
}

public class AsyncResponderFactory {
    public function createResponder( resultHandler:Function, faultHandler:Function ) : AsyncResponder {
        return new AsyncResponder(resultHandler, faultHandler);
    }
}

public class QueryTaskFactory {
    public function createTask (url:String) : QueryTask {
       return new QueryTask(url);
    }
}

...在队列中:

(...)
public var queryFactory:QueryFactory;
public var responderFactory : AsyncResponderFactory;
public var taskFactory:QueryTaskFactory;
(...)
var query:Query = queryFactory.createQuery ( myArgs );
var responder:AsyncResponder = responderFactory.createResponder (resultHandler, faultHandler);
var task:QueryTask = taskFactory.createTask (url);
task.execute (query, responder);
(...)

...在你的测试中:

[Rule]
public var rule : MockolateRule = new MockolateRule();

[Mock]
public var queryFactory:QueryFactory;
public var query:Query; // no need to mock this - you are not calling any of its methods in Queue.
[Mock]
public var responderFactory:AsyncResponderFactory;
public var responder:AsyncResponder;
[Mock]
public var taskFactory:QueryTaskFactory;
[Mock]
public var task:QueryTask;

[Test]
public function myTest () : void {
    query = new Query();
    mock( queryFactory ).method( "createQuery ").args ( (...) ).returns( query ); // specify arguments instead of (...)!

    responder = new AsyncResponder ();
    mock( responderFactory ).method( "createResponder" ).args( isA(Function) , isA(Function) ).returns( responder ); // this will ensure that the handlers are really functions 
    queue.responderFactory = responderFactory;

    mock( task ).method( "execute" ).args( query, responder );
    mock( taskFactory ).method( "createTask" ).args( "http://myurl.com/" ).returns( task );
    queue.taskFactory = taskFactory; 

    queue.doStuff(); // execute whatever the queue should actually do
}

请注意,您必须将所有模拟声明为public,并且必须在将模拟对象传递给其主机之前添加所有期望,否则,mockolate无法正确配置代理对象