Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

arguments and parameters can be used safely. Add setTestArguments and setTestParameters to mock then easily. Improve futurize. #3166

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions example/lib/pages/home/data/home_api_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,8 @@ class HomeProvider extends GetConnect implements IHomeProvider {
Future<Response<List<CountriesItem>>> getCountries() {
return get(
'/countries',
decoder: (data) {
print(data.runtimeType);
final foo = (data as List).map((item) {
return CountriesItem.fromJson(item);
});
print("foo: ${foo.runtimeType}");
return foo.toList();
},
decoder: (data) =>
(data as List).map((item) => CountriesItem.fromJson(item)).toList(),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,7 @@ class HomeController extends StateController<List<CountriesItem>> {
@override
void onInit() {
super.onInit();
change(GetStatus.success([]));
change(GetStatus.loading());
//Loading, Success, Error handle with 1 line of code
try {
futurize(homeRepository.getCountries);
} catch (e) {
print(e);
}
futurize(homeRepository.getCountries);
}

Future<Country> getCountryByName(String name) async {
Expand Down
216 changes: 104 additions & 112 deletions example/test/main_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:get/get_navigation/src/routes/test_kit.dart';
import 'package:get_demo/pages/home/domain/adapters/repository_adapter.dart';
import 'package:get_demo/pages/home/domain/entity/country_model.dart';
import 'package:get_demo/pages/home/presentation/controllers/details_controller.dart';
import 'package:get_demo/pages/home/presentation/controllers/home_controller.dart';
// import 'package:get_demo/routes/app_pages.dart';
// import 'package:get_test/get_test.dart';

// Mock data
const country1 = CountriesItem(
country: 'Lalaland',
countryCode: 'LA',
Expand All @@ -19,136 +20,127 @@ const country2 = CountriesItem(
countryCode: 'LO',
);

// Mock repository for success
class MockRepositorySuccess implements IHomeRepository {
Future<List<CountriesItem>> getCountries() {
return Future.value([
country1,
country2,
]);
}

Future<Country> getCountry(String path) {
return Future.value(Country(
name: 'Lalaland',
countryCode: 'LA',
numberOfPrizes: 3,
averageAgeOfLaureates: 4,
));
}
@override
Future<List<CountriesItem>> getCountries() async => [country1, country2];

@override
Future<Country> getCountry(String path) async => Country(
name: 'Lalaland',
countryCode: 'LA',
numberOfPrizes: 3,
averageAgeOfLaureates: 4,
);
}

// Mock repository for failure
class MockRepositoryFailure implements IHomeRepository {
Future<List<CountriesItem>> getCountries() {
return Future<List<CountriesItem>>.error('error');
}
@override
Future<List<CountriesItem>> getCountries() async =>
Future.error(FetchException('Failed to load countries'));

Future<Country> getCountry(String path) {
return Future<Country>.error('error');
}
@override
Future<Country> getCountry(String path) async =>
Future.error(FetchException('Failed to load country'));
}

class FetchException implements Exception {
final String message;
FetchException(this.message);
}

// Custom bindings
class TestHomeBinding extends Binding {
final IHomeRepository repository;
TestHomeBinding({required this.repository});

@override
List<Bind> dependencies() => [
Bind.lazyPut<IHomeRepository>(() => repository),
Bind.lazyPut<HomeController>(
() => HomeController(homeRepository: Get.find()),
),
];
}

class MockBinding extends Binding {
class TestDetailsBinding extends Binding {
final IHomeRepository repository;
TestDetailsBinding({required this.repository});

@override
List<Bind> dependencies() {
return [
Bind.lazyPut<IHomeRepository>(() => MockRepositorySuccess()),
Bind.lazyPut<HomeController>(
() => HomeController(homeRepository: Get.find()),
)
];
}
List<Bind> dependencies() => [
Bind.lazyPut<IHomeRepository>(() => repository),
Bind.lazyPut<DetailsController>(
() => DetailsController(homeRepository: Get.find()),
),
];
}

void main() {
WidgetsFlutterBinding.ensureInitialized();
setUpAll(() => HttpOverrides.global = null);
final binding = MockBinding();
setUpAll(() {
HttpOverrides.global = null;
GetTestMode.active = true;
});

setUp(() => Get.reset());

test('Test Controller', () async {
/// Controller can't be on memory
expect(() => Get.find<HomeController>(tag: 'success'),
throwsA(const TypeMatcher<String>()));
group('HomeController Tests', () {
test('Success Scenario', () async {
TestHomeBinding(repository: MockRepositorySuccess()).dependencies();
final controller = Get.find<HomeController>();

/// binding will put the controller on memory
binding.dependencies();
expect(controller.initialized, isTrue);

/// recover your controller
HomeController controller = Get.find<HomeController>();
await Future.delayed(const Duration(milliseconds: 200));

/// check if onInit was called
expect(controller.initialized, true);
expect(controller.status.isSuccess, isTrue);
expect(controller.state.length, 2);
expect(controller.state, containsAll([country1, country2]));
});

/// check initial Status
expect(controller.status.isLoading, true);
test('Failure Scenario', () async {
TestHomeBinding(repository: MockRepositoryFailure()).dependencies();
final controller = Get.find<HomeController>();

/// await time request
await Future.delayed(const Duration(milliseconds: 100));
expect(controller.initialized, isTrue);

/// test if status is success
expect(controller.status.isSuccess, true);
expect(controller.state.length, 2);
expect(controller.state.first, country1);
expect(controller.state.last, country2);
await Future.delayed(const Duration(milliseconds: 200));

/// test if status is error
Get.lazyReplace<IHomeRepository>(() => MockRepositoryFailure());
controller = Get.find<HomeController>();
print(controller.getCountryByName('Lalaland'));
// expect(controller.status.isError, true);
// expect(controller.state, null);
expect(controller.status.isError, isTrue);
expect(controller.status.error, isA<FetchException>());
});
});

/// Tests with GetTests
/// TEMPORARILY REMOVED from the null-safetym branch as
/// get_test is not yet null safety.
/* getTest(
"test description",
getPages: AppPages.routes,
initialRoute: AppPages.INITIAL,
widgetTest: (tester) async {
expect('/home', Get.currentRoute);

Get.toNamed('/home/country');
expect('/home/country', Get.currentRoute);

Get.toNamed('/home/country/details');
expect('/home/country/details', Get.currentRoute);

Get.back();

expect('/home/country', Get.currentRoute);
},
);

testGetX(
'GetX test',
widget: GetX<Controller>(
init: Controller(),
builder: (controller) {
return Text("ban:${controller.count}");
},
),
test: (e) {
expect(find.text("ban:0"), findsOneWidget);
},
);

testController<Controller>(
'Controller test',
(controller) {
print('controllllllll ${controller.count}');
},
controller: Controller(),
onInit: (c) {
c.increment();
print('onInit');
},
onReady: (c) {
print('onReady');
c.increment();
},
onClose: (c) {
print('onClose');
},
);*/
group('DetailsController Tests', () {
test('Success Scenario', () async {
TestDetailsBinding(repository: MockRepositorySuccess()).dependencies();
GetTestMode.setTestArguments(country1);
final controller = Get.find<DetailsController>();

expect(controller.initialized, isTrue);

await Future.delayed(const Duration(milliseconds: 200));

expect(controller.status.isSuccess, isTrue);
expect(controller.state.name, 'Lalaland');
expect(controller.state.countryCode, 'LA');
expect(controller.state.numberOfPrizes, 3);
expect(controller.state.averageAgeOfLaureates, 4);
});

test('Failure Scenario', () async {
TestDetailsBinding(repository: MockRepositoryFailure()).dependencies();
GetTestMode.setTestArguments(country1);
final controller = Get.find<DetailsController>();

expect(controller.initialized, isTrue);

await Future.delayed(const Duration(milliseconds: 200));

expect(controller.status.isError, isTrue);
expect(controller.status.error, isA<FetchException>());
});
});
}
38 changes: 29 additions & 9 deletions lib/get_navigation/src/extension_navigation.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/routes/test_kit.dart';

import '../../get.dart';
import 'dialog/dialog_route.dart';
Expand Down Expand Up @@ -1182,10 +1183,6 @@ extension GetNavigationExt on GetInterface {
return key;
}

/// give current arguments
//dynamic get arguments => routing.args;
dynamic get arguments => rootController.rootDelegate.arguments();

/// give name from current route
String get currentRoute => routing.current;

Expand Down Expand Up @@ -1326,14 +1323,37 @@ extension GetNavigationExt on GetInterface {

Routing get routing => _getxController.routing;

set parameters(Map<String, String?> newParameters) =>
rootController.parameters = newParameters;
bool get _shouldUseMock => GetTestMode.active && !GetRoot.treeInitialized;

set testMode(bool isTest) => rootController.testMode = isTest;
/// give current arguments
dynamic get arguments {
return args();
}

bool get testMode => _getxController.testMode;
T args<T>() {
if (_shouldUseMock) {
return GetTestMode.arguments as T;
}
return rootController.rootDelegate.arguments<T>();
}

Map<String, String?> get parameters => rootController.rootDelegate.parameters;
// set parameters(Map<String, String?> newParameters) {
// rootController.parameters = newParameters;
// }

// @Deprecated('Use GetTestMode.active=true instead')
set testMode(bool isTest) => GetTestMode.active = isTest;

// @Deprecated('Use GetTestMode.active instead')
bool get testMode => GetTestMode.active;

Map<String, String?> get parameters {
if (_shouldUseMock) {
return GetTestMode.parameters;
}

return rootController.rootDelegate.parameters;
}

/// Casts the stored router delegate to a desired type
TDelegate? delegate<TDelegate extends RouterDelegate<TPage>, TPage>() =>
Expand Down
5 changes: 4 additions & 1 deletion lib/get_navigation/src/root/get_root.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/routes/test_kit.dart';

import '../../../get.dart';
import '../router_report.dart';
Expand Down Expand Up @@ -287,6 +288,8 @@ class GetRoot extends StatefulWidget {
@override
State<GetRoot> createState() => GetRootState();

static bool get treeInitialized => GetRootState._controller != null;

static GetRootState of(BuildContext context) {
// Handles the case where the input context is a navigator element.
GetRootState? root;
Expand Down Expand Up @@ -432,7 +435,7 @@ class GetRootState extends State<GetRoot> with WidgetsBindingObserver {

set testMode(bool isTest) {
config = config.copyWith(testMode: isTest);
// _getxController.testMode = isTest;
GetTestMode.active = isTest;
}

void onReady() {
Expand Down
2 changes: 1 addition & 1 deletion lib/get_navigation/src/routes/route_middleware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ class PageRedirect {
settings = route;
}
final match = context.delegate.matchRoute(settings!.name!);
Get.parameters = match.parameters;
// Get.parameters = match.parameters;

// No Match found
if (match.route == null) {
Expand Down
Loading
Loading