[英]Use a specific instance of a class inside an isolate
我通过compute()
方法使用隔离来从 API(大约 10k 个条目)中获取、解析和排序数据。
My method getAllCards()
is defined inside a class YgoProRepositoryImpl
which has an instance of my remote datasource class YgoProRemoteDataSource
it is in this class that the method to call my API is defined (it is a simple GET request).
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
,因为它总是会从我的服务定位器中获得引用。
如何不依赖我的服务定位器将YgoProRemoteDataSource
注入到我的隔离中并使getAllCards()
可测试?
据我了解,您有两个选择,要么通过参数注入static Future<List<YgoCard>> _fetchCards(_) async
所需的依赖项,要么在定位器本身中模拟 object 。 我会 go 作为第一个选项,并且有类似的东西:
static Future<List<YgoCard>> _fetchCards(_,YgoProRemoteDataSource remote) async {
// No need to set up locator as you passed the needed dependencies
// setupLocator();
final cards = await remote
.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;
}
刚刚更新了答案,因为在这里编辑它比在评论中更容易......
嗯,我能想到的唯一解决方法是将 setupLocator() function 作为参数传递给 class YgoProRepositoryImpl:
final Function setupLocator;
YgoProRepositoryImpl({
required this.remoteDataSource,
required this.setupLocator;
// ...
});
这样,您可以传递一个模拟来设置模拟类或service_locator.dart
的真实setupLocator
。 这可能不太优雅。 但它应该使它可测试,因为现在你可以模拟设置并且它没有在 function 中硬编码
你真的需要测试getCards()
function 吗? 你在那里真正测试什么? 该compute
有效,当然希望 Dart SDK 团队对此进行测试。
_fetchCards()
和setupLocator()
也不需要测试,这是您的测试逻辑的先决条件。 无论如何,您都想更改测试的设置。
所以你真正想要测试的是获取和排序。 将其重组为可测试的 static function 并预先设置您的定位器。 在上面放一个@visibleForTesting
注释。
另外,根据您在服务定位器中绑定的数量,这对于之后仅使用一个存储库可能会产生巨大的开销。
例子:
static Future<List<YgoCard>> _fetchCards(_) async {
// As I'm inside an isolate I need to re-setup my locator
setupLocator();
return reallyFetchCards();
}
@visibleForTesting
static Future<List<YgoCard>> reallyFetchCards() async {
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;
}
测试:
// Setup SL and datasource
...
final cards = await YgoProRepositoryImpl.reallyFetchCrads();
// Expect stuff
不是最干净的选项,但您可以在定位器主体中使用Platform.environment.containsKey('FLUTTER_TEST')
环境变量来查看代码是否正在测试 session 中执行并调整正在实例化的内容。
请注意,您需要使用flutter_test
package 而不是 Dart 的标准test
(我相信是这种情况,因为您已经在使用compute()
,它是 Flutter SDK 的一部分)。
做了更认真的尝试,请看repo: https://github.com/maxim-saplin/compute_sl_test_sample
Essentially with the current state of affairs with Flutter/Dart you can't pass neither closures nor classes containing closures across isolates boundaries (yet that might change when newer features in Dart land Flutter https://github.com/dart-lang/sdk /issues/46623#issuecomment-916161528 )。 这意味着如果您不希望任何测试代码成为发布版本的一部分,则您无法传递服务定位器(包含闭包)或欺骗隔离以通过闭包实例化定位器的测试版本。 然而,您可以轻松地将数据源实例传递给隔离,以在其入口点作为参数使用。
此外,我认为要求隔离来重建整个服务定位器是没有意义的。 compute() 背后的整个想法是创建一个短的离开隔离,运行计算,返回结果并终止隔离。 初始化定位器是一种开销,最好避免。 此外,compute() 的整个概念似乎与应用程序的 rest 尽可能隔离。
您可以克隆存储库并运行测试。 关于样本的几句话:
lib/classes.dart
重新创建您提供的代码片段test/widget_test.dart
验证YgoProRepositoryImpl
在隔离运行假数据源版本时工作正常YgoProRepositoryImpl
模仿真实实现,位于 classes.dart 和YgoProRepositoryFake
模仿测试版tester.runAsync()
中包装测试主体,以便进行实时异步执行(而不是测试默认使用的假异步并依赖泵送来推进测试时间)。 在这种模式下运行测试可能会很慢(实际上有 0.5 秒的等待时间),以不使用 compute() 或未在许多测试中测试的方式构建测试是合理的
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.