測試每一層
測試 UI 層
#判斷您的架構是否健全的一種方法是考量應用程式測試的難易程度。由於 view model 和 view 具有明確的輸入,它們的依賴關係可以輕鬆地被 mock 或 fake,並且可以輕鬆地編寫單元測試。
ViewModel 單元測試
#要測試 view model 的 UI 邏輯,您應該編寫不依賴 Flutter 函式庫或測試框架的單元測試。
儲存庫是 view model 唯一的依賴項(除非您正在實作使用案例),而編寫儲存庫的 mocks
或 fakes
是您唯一需要做的設定。在這個測試範例中,使用了一個名為 FakeBookingRepository
的 fake。
void main() {
group('HomeViewModel tests', () {
test('Load bookings', () {
// HomeViewModel._load is called in the constructor of HomeViewModel.
final viewModel = HomeViewModel(
bookingRepository: FakeBookingRepository()
..createBooking(kBooking),
userRepository: FakeUserRepository(),
);
expect(viewModel.bookings.isNotEmpty, true);
});
});
}
FakeBookingRepository
類別實作了BookingRepository
。在本案例研究的資料層章節中,詳細解釋了 BookingRepository
類別。
class FakeBookingRepository implements BookingRepository {
List<Booking> bookings = List.empty(growable: true);
@override
Future<Result<void>> createBooking(Booking booking) async {
bookings.add(booking);
return Result.ok(null);
}
// ...
}
View Widget 測試
#一旦您為 view model 編寫了測試,您也已經建立了編寫 widget 測試所需的 fakes。以下範例顯示如何使用 HomeViewModel
和所需的儲存庫來設定 HomeScreen
widget 測試。
void main() {
group('HomeScreen tests', () {
late HomeViewModel viewModel;
late MockGoRouter goRouter;
late FakeBookingRepository bookingRepository;
setUp(() {
bookingRepository = FakeBookingRepository()
..createBooking(kBooking);
viewModel = HomeViewModel(
bookingRepository: bookingRepository,
userRepository: FakeUserRepository(),
);
goRouter = MockGoRouter();
when(() => goRouter.push(any())).thenAnswer((_) => Future.value(null));
});
// ...
});
}
此設定建立了所需的兩個 fake 儲存庫,並將它們傳遞到 HomeViewModel
物件。此類別不需要被 fake。
在定義了 view model 及其依賴項後,需要建立將被測試的 Widget 樹狀結構。在 HomeScreen
的測試中,定義了一個 loadWidget
方法。
void main() {
group('HomeScreen tests', () {
late HomeViewModel viewModel;
late MockGoRouter goRouter;
late FakeBookingRepository bookingRepository;
setUp(
// ...
);
void loadWidget(WidgetTester tester) async {
await testApp(
tester,
ChangeNotifierProvider.value(
value: FakeAuthRepository() as AuthRepository,
child: Provider.value(
value: FakeItineraryConfigRepository() as ItineraryConfigRepository,
child: HomeScreen(viewModel: viewModel),
),
),
goRouter: goRouter,
);
}
// ...
});
}
此方法會轉而呼叫 testApp
,這是一個用於 compass 應用程式中所有 widget 測試的通用方法。它看起來像這樣
void testApp(
WidgetTester tester,
Widget body, {
GoRouter? goRouter,
}) async {
tester.view.devicePixelRatio = 1.0;
await tester.binding.setSurfaceSize(const Size(1200, 800));
await mockNetworkImages(() async {
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: [
GlobalWidgetsLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
AppLocalizationDelegate(),
],
theme: AppTheme.lightTheme,
home: InheritedGoRouter(
goRouter: goRouter ?? MockGoRouter(),
child: Scaffold(
body: body,
),
),
),
);
});
}
此函式的唯一工作是建立一個可以測試的 widget 樹狀結構。
loadWidget
方法傳入 widget 樹狀結構中用於測試的獨特部分。在這種情況下,包括 HomeScreen
和它的 view model,以及 widget 樹狀結構中較高層級的一些額外 fake 儲存庫。
最重要的是,如果您的架構健全,view 和 view model 測試僅需要 mock 儲存庫。
測試資料層
#與 UI 層類似,資料層的元件具有明確的輸入和輸出,使兩側都可 fake。要為任何給定的儲存庫編寫單元測試,請 mock 它所依賴的服務。以下範例顯示了 BookingRepository
的單元測試。
void main() {
group('BookingRepositoryRemote tests', () {
late BookingRepository bookingRepository;
late FakeApiClient fakeApiClient;
setUp(() {
fakeApiClient = FakeApiClient();
bookingRepository = BookingRepositoryRemote(
apiClient: fakeApiClient,
);
});
test('should get booking', () async {
final result = await bookingRepository.getBooking(0);
final booking = result.asOk.value;
expect(booking, kBooking);
});
});
}
要了解更多關於編寫 mocks 和 fakes 的資訊,請查看Compass App testing
目錄中的範例,或閱讀Flutter 的測試文件。
意見回饋
#由於本網站的這一部分正在發展中,我們歡迎您的意見回饋!
除非另有說明,本網站上的文件反映了 Flutter 的最新穩定版本。頁面最後更新於 2024-12-04。 檢視原始碼 或 回報問題。