Как я могу смоделировать AsyncResponder и результирующие функции-обработчики на смоделированном объекте, используя FlexUnit 4 и mockolate
Я пытаюсь написать некоторые модульные тесты для класса, который я пишу во Flex 4.5.1, используя FlexUnit 4 и Mockolate для моей среды тестирования и моделирования соответственно. Я использую as3-сигналы для своих пользовательских событий.
Функциональность, которую я пишу и тестирую, представляет собой класс-оболочку (QueryQueue) вокруг класса QueryTask в ArcGIS API for Flex. Это позволяет мне легко ставить в очередь несколько задач для выполнения. Моя обертка, 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
событие отправлено. Тест должен гарантировать, что:
execute
вызывается на каждый запрос один раз и только один разinProgress = true
пока результаты не обработаныinProgress = false
когда результаты были обработаныcompleted
отправляется после обработки результатовcanceled
никогда не вызывается (для действительных запросов)- Обработка, выполненная в очереди, корректно обрабатывает и упаковывает результаты запроса
До сих пор я могу написать тесты для пунктов с 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 выше, что я хочу сделать, это проверить, что данные, которые возвращаются в queryTaskResultHandler
и queryTaskFaultHandler
правильно обрабатывается.
То есть я не отправляю completed
событие, пока не будут возвращены все ответы на запросы, включая успешный и неудачный результат.
Чтобы протестировать этот процесс, я думаю, что мне нужно смоделировать данные, возвращающиеся в результате, и обработчики ошибок для каждой смоделированной задачи запроса.
Итак, как мне высмеивать данные, передаваемые в обработчик результатов, созданный с помощью AsyncResponder
используя FlexUnit и mockolate.
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
,
Теперь для вашего особого случая, на мой взгляд, было бы лучше высмеять использование всех трех внешних зависимостей: Query
, QueryTask
а также AsyncResponder
, поскольку вы тестируете не их функциональность, а 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 не сможет правильно настроить прокси-объекты.