Cookie и фактические сервисные вызовы будут проверены в интеграционных тестах.Вы правы, считая, что эти биты не подходят для модульного теста.
Что мне нравится делать, так это отличный делегат (при условии, что ваши точки интеграции еще не выражают интерфейс) и создавать тестовые делегаты, которые реализуют выбранный мной интерфейс.Файл cookie будет «извлечен» и ВСЕГДА будет действителен для целей вашего теста.Если вам нужно, чтобы оно было ложным, создайте другой тестовый делегат и укажите его как зависимость.
Mockolate может иметь большое значение, но ключом является создание твердых mock.
Так что это пример.Он не отвечает на ваш вопрос напрямую, но я думаю, что он представляет некоторые из тех же проблем, и, следовательно, может помочь вам решить конкретный набор обстоятельств.
Я использую стороннюю библиотеку для обработки операций SQLв этом приложении.Библиотека имеет базовый класс под названием SQLRunner.Этот класс, как это бывает, не имеет интерфейса (что может немного облегчить задачу).У меня было два варианта.
- Форк и изменить библиотеку для экспресс-интерфейсов.
- Обернуть SQLRunner в свой собственный класс делегата.
Как это происходитЯ сделал оба, но предпочел второй подход по ряду причин.Я могу полностью определить API и функциональность сторонней библиотеки.Это круто.Я на самом деле не менял API, но если вам не нравится, как он называет методы ... не беспокойтесь, измените его.Это также позволило мне выразить интерфейс!Первоначально я сделал это, потому что я хотел использовать Mockolate, чтобы высмеивать его использование.Это также пригодилось для создания моих собственных тестовых макетов, которые имели более надежные возможности и были более понятными.Итак, вот сервис:
public class SQLTaskService extends Actor implements ITaskService
{
[Inject]
public var sqlRunner:ISQLRunnerDelegate;
[Inject]
public var statusListModel:StatusListModel;
[Inject]
public var taskListModel:TaskListModel;
public function loadAllTasks():void
{
statusListModel.removeAllTasks();
sqlRunner.execute(LOAD_ALL_TASKS_SQL, null, loadAllTasksResultHandler, Task, databaseErrorHandler);
}
private function loadAllTasksResultHandler(result:SQLResult):void
{
for each(var task:Task in result.data)
{
var taskStatus:Status = statusListModel.getStatusFromId(task.statusId);
statusListModel.addTaskToStatus(task, taskStatus);
taskListModel.addTask(task);
}
}
public function loadTaskById(id:int):void
{
sqlRunner.execute(LOAD_TASK_SQL, {taskId:id}, loadTaskResultHandler, Task);
}
private function loadTaskResultHandler(result:SQLResult):void
{
var task:Task = result.data[0] as Task;
var taskStatus:Status = statusListModel.getStatusFromId(task.statusId);
task = taskListModel.updateTask(task);
statusListModel.addTaskToStatus(task, taskStatus);
}
public function save(task:Task):void
{
var params:Object = task.toParamObject();
sqlRunner.executeModify(Vector.<QueuedStatement>(
[new QueuedStatement(SAVE_TASK_SQL, params)]), saveTaskResultHandler, databaseErrorHandler);
}
private function saveTaskResultHandler(results:Vector.<SQLResult>):void
{
var result:SQLResult = results[0];
if (result.rowsAffected > 0)
{
var id:Number = result.lastInsertRowID;
loadTaskById(id);
}
}
public function deleteTask(task:Task):void
{
sqlRunner.executeModify(Vector.<QueuedStatement>([new QueuedStatement(DELETE_TASK_SQL, {taskId:task.taskId})]),
deleteTaskResult, databaseErrorHandler);
}
private function deleteTaskResult(results:Vector.<SQLResult>):void
{
//pass
}
private function databaseErrorHandler(error:SQLError):void
{
dispatch(new DatabaseErrorHandlerEvent(error.message));
}
[Embed(source="/assets/data/sql/tasks/SaveTask.sql", mimeType="application/octet-stream")]
private static const SaveTaskStatementText:Class;
public static const SAVE_TASK_SQL:String = new SaveTaskStatementText();
[Embed(source="/assets/data/sql/tasks/DeleteTask.sql", mimeType="application/octet-stream")]
private static const DeleteTaskStatementText:Class;
public static const DELETE_TASK_SQL:String = new DeleteTaskStatementText();
[Embed(source="/assets/data/sql/tasks/LoadTask.sql", mimeType="application/octet-stream")]
private static const LoadTaskStatementText:Class;
public static const LOAD_TASK_SQL:String = new LoadTaskStatementText();
[Embed(source="/assets/data/sql/tasks/LoadAllTasks.sql", mimeType="application/octet-stream")]
private static const LoadAllTasksStatementText:Class;
public static const LOAD_ALL_TASKS_SQL:String = new LoadAllTasksStatementText();
}
вы можете видеть, что он имеет зависимость делегата:
/**
* This is a delegate for the SQLRunner class that allows us to utilize an interface
* for the purposes of creating mocks. The actual SQLRunner class does not express
* an interface. This approach also allows us to encapsulate the usage of a 3rd party
* library into this single delegate.
*
* <p>An alternative would be to fork and modify the original library, which would
* definitely be a viable option and would help others in the future.</p>
*/
public class SQLRunnerDelegate implements ISQLRunnerDelegate
{
private var sqlRunner:SQLRunner;
public function SQLRunnerDelegate(dataBaseFile:File, maxPoolSize:int = 5)
{
sqlRunner = new SQLRunner(dataBaseFile, maxPoolSize);
}
public function get numConnections():int
{
return sqlRunner.numConnections;
}
public function get connectionErrorHandler():Function
{
return sqlRunner.connectionErrorHandler;
}
public function set connectionErrorHandler(value:Function):void
{
sqlRunner.connectionErrorHandler = value;
}
public function execute(sql:String, parameters:Object, handler:Function, itemClass:Class = null, errorHandler:Function = null):void
{
sqlRunner.execute(sql, parameters, handler, itemClass, errorHandler);
}
public function executeModify(statementBatch:Vector.<QueuedStatement>, resultHandler:Function, errorHandler:Function, progressHandler:Function = null):void
{
sqlRunner.executeModify(statementBatch, resultHandler, errorHandler, progressHandler);
}
public function close(resultHandler:Function, errorHandler:Function = null):void
{
sqlRunner.close(resultHandler, errorHandler);
}
}
Это сработало очень хорошо.Теперь у моего приложения есть одна маленькая точка интеграции со сторонней библиотекой.Большая победа.Я также получаю надежные макеты, не имея дело с зависимостями в файловой системе или любыми другими странностями сторонней библиотеки:
/**
* This is a more robust mock for the SQLRunnerDelegate to test for
* side effects that occur when methods are called on SQLTaskService
*/
public class MockTaskSQLRunnerDelegate extends MockSQLRunnerDelegateBase implements ISQLRunnerDelegate
{
public function execute(sql:String, parameters:Object, handler:Function, itemClass:Class = null, errorHandler:Function = null):void
{
lastStatementExecuted = sql;
allStatementsExecuted.push(lastStatementExecuted);
parametersSent = parameters;
switch (sql)
{
case SQLTaskService.LOAD_ALL_TASKS_SQL:
handler.call(null, loadTask());
break;
case SQLTaskService.LOAD_TASK_SQL:
handler.call(null, loadTask());
break;
default:
break;
}
}
private function loadTask():SQLResult
{
var task:Task = new Task();
var data:Array = [task];
var result:SQLResult = new SQLResult(data);
task.taskId = 1;
task.statusId = 1;
return result;
}
public function executeModify(statementBatch:Vector.<QueuedStatement>, resultHandler:Function, errorHandler:Function, progressHandler:Function = null):void
{
lastStatementExecuted = statementBatch[0].statementText;
allStatementsExecuted.push(lastStatementExecuted);
parametersSent = statementBatch[0].parameters;
switch (lastStatementExecuted)
{
case SQLTaskService.SAVE_TASK_SQL:
resultHandler.call(null, saveTask());
break;
}
}
private function saveTask():Vector.<SQLResult>
{
var task:Task = new Task();
var result:SQLResult = new SQLResult([task], 1, true, 1);
var results:Vector.<SQLResult> = new Vector.<SQLResult>();
task.taskId = task.statusId = 1;
results.push(result);
return results;
}
public function get numConnections():int
{
return 0;
}
public function get connectionErrorHandler():Function
{
return null;
}
public function set connectionErrorHandler(value:Function):void
{
}
public function close(resultHandler:Function, errorHandler:Function = null):void
{
}
}
И я получил хороший набор тестов из сделки:
public class SqlTaskServiceTest
{
private var taskService:SQLTaskService;
[Before(async)]
public function setup():void
{
taskService = new SQLTaskService();
taskService.statusListModel = new StatusListModel();
taskService.taskListModel = new TaskListModel();
initializeModels();
prepareMockolates();
}
public function prepareMockolates():void
{
Async.proceedOnEvent(this, prepare(ISQLRunnerDelegate), Event.COMPLETE);
}
[Test]
public function loadAllTasks_executesSqlStatement_statementEqualsLoadAll():void
{
var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
taskService.sqlRunner = runner;
taskService.loadAllTasks();
assertThat(runner.lastStatementExecuted, equalTo(SQLTaskService.LOAD_ALL_TASKS_SQL));
}
[Test]
public function loadAllTasks_clearsTasksFromStatusListModel_lengthIsEqualToZero():void
{
var status:Status = new Status();
var task:Task = new Task();
initializeModels(status, task);
taskService.sqlRunner = nice(ISQLRunnerDelegate);
taskService.loadAllTasks();
assertThat(status.tasks.length, equalTo(0))
}
[Test]
public function loadAllTasks_updatesTaskListModelWithLoadedTasks_collectionLengthIsOne():void
{
taskService.sqlRunner = new MockTaskSQLRunnerDelegate();
taskService.loadAllTasks();
assertThat(taskService.taskListModel.tasks.length, equalTo(1));
}
[Test]
public function loadAllTasks_updatesStatusWithTask_statusHasTasks():void
{
var status:Status = new Status();
initializeModels(status);
taskService.sqlRunner = new MockTaskSQLRunnerDelegate();
taskService.loadAllTasks();
assertThat(status.tasks.length, greaterThan(0));
}
[Test]
public function save_executesSqlStatement_statementEqualsSave():void
{
var task:Task = new Task();
var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
taskService.sqlRunner = runner;
task.statusId = 1;
taskService.save(task);
assertThat(runner.allStatementsExecuted, hasItem(SQLTaskService.SAVE_TASK_SQL));
}
[Test]
public function save_taskIsLoadedAfterSave_statementEqualsLoad():void
{
var task:Task = new Task();
var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
taskService.sqlRunner = runner;
task.statusId = 1;
taskService.save(task);
assertThat(runner.allStatementsExecuted, hasItem(SQLTaskService.LOAD_TASK_SQL));
}
[Test]
public function save_taskIsAddedToModelWhenNew_tasksLengthGreaterThanZero():void
{
var taskListModel:TaskListModel = taskService.taskListModel;
var task:Task = new Task();
taskListModel.reset();
taskService.sqlRunner = new MockTaskSQLRunnerDelegate();
task.statusId = 1;
task.taskId = 1;
taskService.save(task);
assertThat(taskListModel.tasks.length, equalTo(1));
}
[Test]
public function save_existingTaskInstanceIsUpdatedAfterSave_objectsAreStrictlyEqual():void
{
var taskListModel:TaskListModel = taskService.taskListModel;
var task:Task = new Task();
var updatedTask:Task;
taskListModel.addTask(task);
taskService.sqlRunner = new MockTaskSQLRunnerDelegate();
task.statusId = 1;
task.taskId = 1;
taskService.save(task);
updatedTask = taskListModel.getTaskById(task.taskId);
assertThat(updatedTask, strictlyEqualTo(task));
}
[Test]
public function loadTaskById_executesLoadStatement_statementEqualsLoad():void
{
var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
taskService.sqlRunner = runner;
taskService.loadTaskById(1);
assertThat(runner.allStatementsExecuted, hasItem(SQLTaskService.LOAD_TASK_SQL));
}
[Test]
public function deleteTasks_executesDeleteStatement_statementEqualsDelete():void
{
var task:Task = new Task();
var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
taskService.sqlRunner = runner;
taskService.deleteTask(task);
assertThat(runner.allStatementsExecuted, hasItem(SQLTaskService.DELETE_TASK_SQL));
}
private function initializeModels(status:Status = null, task:Task = null):void
{
var statusListModel:StatusListModel = taskService.statusListModel;
statusListModel.reset();
//if nothing was passed in we need to default to new objects
status ||= new Status();
task ||= new Task();
status.statusId = 1;
task.taskId = task.statusId = 1;
statusListModel.statuses.addItem(status);
statusListModel.addTaskToStatus(task, status);
}
}
Примечание: Хочу отметить, что не существует ни одного асинхронного теста .Редко возникает необходимость запуска асинхронных юнит-тестов.Есть крайние случаи, когда вам действительно может понадобиться, но они являются исключением.