Как можно протестировать метод, который возвращает Future
до завершения тестового бегуна? У меня проблема, когда мой бегун unit test завершается до завершения асинхронных методов.
Как мне проверить будущее в дартс?
Ответ 1
Полный пример тестирования с помощью completion
заключается в следующем.
import 'package:unittest/unittest.dart';
class Compute {
Future<Map> sumIt(List<int> data) {
Completer completer = new Completer();
int sum = 0;
data.forEach((i) => sum += i);
completer.complete({"value" : sum});
return completer.future;
}
}
void main() {
test("testing a future", () {
Compute compute = new Compute();
Future<Map> future = compute.sumIt([1, 2, 3]);
expect(future, completion(equals({"value" : 6})));
});
}
Бегун unit test может не завершиться до завершения этого кода. Таким образом, казалось бы, что unit test выполняется правильно. С Future
, который может потребовать более длительные периоды времени, чтобы закончить правильный путь, следует использовать completion
-сервер, доступный в пакете unittest.
/**
* Matches a [Future] that completes succesfully with a value that matches
* [matcher]. Note that this creates an asynchronous expectation. The call to
* `expect()` that includes this will return immediately and execution will
* continue. Later, when the future completes, the actual expectation will run.
*
* To test that a Future completes with an exception, you can use [throws] and
* [throwsA].
*/
Matcher completion(matcher) => new _Completes(wrapMatcher(matcher));
Возникло бы желание сделать следующее, что было бы неправильным способом модульного тестирования возвращаемого будущего в дротике. ПРЕДУПРЕЖДЕНИЕ: ниже приведен неверный способ проверки фьючерсов.
import 'package:unittest/unittest.dart';
class Compute {
Future<Map> sumIt(List<int> data) {
Completer completer = new Completer();
int sum = 0;
data.forEach((i) => sum+=i);
completer.complete({"value":sum});
return completer.future;
}
}
void main() {
test("testing a future", () {
Compute compute = new Compute();
compute.sumIt([1, 2, 3]).then((Map m) {
Expect.equals(true, m.containsKey("value"));
Expect.equals(6, m["value"]);
});
});
}
Ответ 2
Другая возможность - использовать функцию expectAsync1. Рабочий аналог для исходного неправильного варианта теста:
void main() {
test("testing a future", () {
Compute compute = new Compute();
compute.sumIt([1, 2, 3]).then(expectAsync1((Map m) {
Expect.equals(true, m.containsKey("value"));
Expect.equals(6, m["value"]);
}));
});
}
Одним из преимуществ использования expectAsync1 для асинхронного тестирования является его способность к компоновке. Иногда тесты, естественно, нуждаются в нескольких последовательных асинхронных блоках кода. Пример теста из mongo_db:
testCursorGetMore(){
var res;
Db db = new Db('${DefaultUri}mongo_dart-test');
DbCollection collection;
int count = 0;
Cursor cursor;
db.open().chain(expectAsync1((c){
collection = db.collection('new_big_collection2');
collection.remove();
return db.getLastError();
})).chain(expectAsync1((_){
cursor = new Cursor(db,collection,where.limit(10));
return cursor.each((v){
count++;
});
})).chain(expectAsync1((dummy){
expect(count,0);
List toInsert = new List();
for (int n=0;n < 1000; n++){
toInsert.add({"a":n});
}
collection.insertAll(toInsert);
return db.getLastError();
})).chain(expectAsync1((_){
cursor = new Cursor(db,collection,where.limit(10));
return cursor.each((v)=>count++);
})).then(expectAsync1((v){
expect(count,1000);
expect(cursor.cursorId,0);
expect(cursor.state,Cursor.CLOSED);
collection.remove();
db.close();
}));
}
Обновление:
Оба API будущего и unittest были изменены с тех пор, как был задан вопрос.
Теперь можно просто вернуть Future
из тестовой функции, и unittest правильно выполнил ее со всеми функциями, защищенными асинхронным доступом.
В сочетании с тем, что методы chain
и then
Future
теперь объединены, что обеспечивает хороший синтаксис для тестов с несколькими последовательными блоками кода. В текущей версии mongo_dart такой же тест выглядит следующим образом:
Future testCursorGetMore(){
var res;
Db db = new Db('${DefaultUri}mongo_dart-test');
DbCollection collection;
int count = 0;
Cursor cursor;
return db.open().then((c){
collection = db.collection('new_big_collection2');
collection.remove();
return db.getLastError();
}).then((_){
cursor = new Cursor(db,collection,where.limit(10));
return cursor.forEach((v){
count++;
});
}).then((dummy){
expect(count,0);
List toInsert = new List();
for (int n=0;n < 1000; n++){
toInsert.add({"a":n});
}
collection.insertAll(toInsert);
return db.getLastError();
}).then((_){
cursor = new Cursor(db,collection,null);
return cursor.forEach((v)=>count++);
}).then((v){
expect(count,1000);
expect(cursor.cursorId,0);
expect(cursor.state,State.CLOSED);
collection.remove();
return db.close();
});
}
Ответ 3
В качестве альтернативы, вот что я делаю. Это похоже на ответы выше:
test('get by keys', () {
Future future = asyncSetup().then((_) => store.getByKeys(["hello", "dart"]));
future.then((values) {
expect(values, hasLength(2));
expect(values.contains("world"), true);
expect(values.contains("is fun"), true);
});
expect(future, completes);
});
Я получаю ссылку на будущее и поставлю все свои ожидающие утверждения внутри вызова then
. Затем я регистрирую expect(future, completes)
, чтобы убедиться, что он действительно завершен.
Ответ 4
См. раздел асинхронных тестов в article или документации API для expectAsync.
Ниже приведен краткий пример. Обратите внимание, что expectAsync() должен быть вызван до того, как закрытие передано в test().
import 'package:unittest/unittest.dart';
checkProgress() => print('Check progress called.');
main() {
test('Window timeout test', () {
var callback = expectAsync(checkProgress);
new Timer(new Duration(milliseconds:100), callback);
});
}
Другой способ подождать, когда будущее будет завершено во время теста, - это вернуть его из закрытия, переданного в тестовую функцию. См. Этот пример из статьи, приведенной выше:
import 'dart:async';
import 'package:unittest/unittest.dart';
void main() {
test('test that time has passed', () {
var duration = const Duration(milliseconds: 200);
var time = new DateTime.now();
return new Future.delayed(duration).then((_) {
var delta = new DateTime.now().difference(time);
expect(delta, greaterThanOrEqualTo(duration));
});
});
}
Ответ 5
Для mockito
v. 2+ Существует возможность сделать это с помощью
await untilCalled(mockObject.someMethod())