我通过compute()
方法使用一个isolate来从一个API(大约10k个条目)中获取、解析和排序数据。
我的方法getAllCards()
是在一个类YgoProRepositoryImpl
中定义的,这个类有一个我的远程数据源类YgoProRemoteDataSource
的示例,在这个类中定义了调用我的API的方法(它是一个简单的GET请求)。
代码示例
ygopro_repository_impl.dart
:
class YgoProRepositoryImpl implements YgoProRepository {
final YgoProRemoteDataSource remoteDataSource;
// ...
YgoProRepositoryImpl({
required this.remoteDataSource,
// ...
});
// ...
static Future<List<YgoCard>> _fetchCards(_) async {
// As I'm inside an isolate I need to re-setup my locator
setupLocator();
final cards = await sl<YgoProRemoteDataSource>()
.getCardInfo(GetCardInfoRequest(misc: true));
cards.sort((a, b) => a.name.compareTo(b.name));
return cards;
}
@override
Future<List<YgoCard>> getAllCards() async {
final cards = await compute(_fetchCards, null);
return cards;
}
// ...
}
service_locator.dart
:
import 'package:get_it/get_it.dart';
import 'data/api/api.dart';
import 'data/datasources/remote/ygopro_remote_data_source.dart';
import 'data/repository/ygopro_repository_impl.dart';
import 'domain/repository/ygopro_repository.dart';
final sl = GetIt.instance;
void setupLocator() {
// ...
_configDomain();
_configData();
// ...
_configExternal();
}
void _configDomain() {
//! Domain
// ...
// Repository
sl.registerLazySingleton<YgoProRepository>(
() => YgoProRepositoryImpl(
remoteDataSource: sl(),
// ...
),
);
}
void _configData() {
//! Data
// Data sources
sl.registerLazySingleton<YgoProRemoteDataSource>(
() => YgoProRemoteDataSourceImpl(sl<RemoteClient>()),
);
// ...
}
void _configExternal() {
//! External
sl.registerLazySingleton<RemoteClient>(() => DioClient());
// ...
}
代码工作正常,但getAllCards()
不可测试,因为我不能在隔离中注入YgoProRemoteDataSource
的模拟类,因为它总是从我的服务定位器获得引用。
如何才能不依赖服务定位器将YgoProRemoteDataSource
注入隔离菌株并使getAllCards()
可测试?
3条答案
按热度按时间brjng4g31#
做了一个更严重的尝试,请参阅回购:https://github.com/maxim-saplin/compute_sl_test_sample
从本质上讲,在Flutter/Dart的当前状态下,你不能跨隔离边界传递闭包或包含闭包的类(但当Dart中的新功能登陆Flutter时,这可能会改变https://github.com/dart-lang/sdk/issues/46623#issuecomment-916161528)。这意味着,如果您不希望任何测试代码成为发布构建的一部分,则无法传递服务定位器(包含闭包)或欺骗隔离以通过闭包示例化定位器的测试版本。但是,您可以轻松地将数据源示例传递给隔离,以便在其入口点作为参数使用。
此外,我不认为要求isolate重新构建整个服务定位器是有意义的。compute()背后的整个思想是创建一个短的离开隔离,运行计算,返回结果并终止隔离。初始化定位器是最好避免的开销。此外,compute()的整个概念似乎与应用程序的其余部分尽可能隔离。
您可以克隆存储库并运行测试。关于样品的几句话:
lib/classes.dart
重新创建您提供的代码段test/widget_test.dart
验证YgoProRepositoryImpl
在隔离运行伪造版本的数据源时工作正常YgoProRemoteDataSourceImpl
模拟真实的实现,位于classes.dart和YgoProRemoteDataSourceFake
模拟测试版本tester.runAsync()
中 Package 测试主体,以便真实的异步执行(而不是测试默认使用的假异步,并依赖于泵来推进测试时间)。在这种模式下运行测试可能会很慢(实际上有0.5秒的等待时间),以一种不使用compute()或在许多测试中没有测试的方式来构建测试是合理的classes.dart
:widget_test.dart
:carvr3hs2#
你真的需要测试
getCards()
函数吗?你到底在测试什么?compute
可以工作,当然希望Dart SDK团队对此进行测试。剩下的就是
_fetchCards()
,setupLocator()
也不需要测试,这是测试逻辑的前提条件。无论如何,您都希望更改测试的设置。所以你真正想测试的是抓取和排序。将其重新构造成一个可测试的静态函数,并预先设置定位器。在上面放一个
@visibleForTesting
注解。顺便说一下,根据您在服务定位器中绑定的数量,这可能是之后仅使用一个存储库的巨大开销。
示例:
测试:
cu6pst1q3#
据我所知,您有两种选择,要么通过参数注入
static Future<List<YgoCard>> _fetchCards(_) async
所需的依赖项,要么在定位器本身中模拟对象。我会选择第一个选项,并有如下内容:编辑
刚刚更新了答案,因为在这里编辑这个比在评论中更容易...
嗯,我能想到的唯一解决办法是将setupLocator()函数作为参数传递给类YgoProRepositoryImpl:
通过这种方式,您可以传递一个设置模拟类的模拟或
service_locator.dart
的真实的setupLocator
。这可能不太优雅。但它应该使它可测试,因为现在你可以模拟设置和它没有硬编码的功能