Skip to content

Commit

Permalink
Merge pull request #3166 from jonataslaw/safe-tests
Browse files Browse the repository at this point in the history
arguments and parameters can be used safely. Add setTestArguments and setTestParameters to mock then easily. Improve futurize.
  • Loading branch information
jonataslaw authored Aug 13, 2024
2 parents c8d708c + a0adb94 commit 5da0cb6
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 144 deletions.
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

0 comments on commit 5da0cb6

Please sign in to comment.