[英]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.