Skip to content

Commit

Permalink
basic testing framework
Browse files Browse the repository at this point in the history
  • Loading branch information
fedecor9 committed Aug 1, 2024
1 parent 511814f commit 5c15e70
Show file tree
Hide file tree
Showing 12 changed files with 284 additions and 22 deletions.
30 changes: 24 additions & 6 deletions design_system/lib/theme/app_text_styles.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//ignore_for_file: unused-files, unused-code

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';

const FontWeight _semiboldWeight = FontWeight.w600;

class AppTextStyles extends TextTheme {
final _isTesting = Platform.environment.containsKey('FLUTTER_TEST');

class AppTextStyles extends TextTheme {
const AppTextStyles({
super.headlineLarge,
super.headlineMedium,
Expand Down Expand Up @@ -45,12 +48,17 @@ class AppTextStyles extends TextTheme {
double fontSize,
FontWeight fontWeight,
) =>
GoogleFonts.roboto(
fontSize: fontSize,
fontWeight: fontWeight,
);
_isTesting
? TextStyle(fontSize: fontSize, fontWeight: fontWeight)
: GoogleFonts.roboto(
fontSize: fontSize,
fontWeight: fontWeight,
);

static AppTextStyles getDefaultAppStyles() => AppTextStyles.fromTextTheme(
static AppTextStyles getDefaultAppStyles() =>
_isTesting ? _testingTextTheme() : _appTextTheme();

static AppTextStyles _appTextTheme() => AppTextStyles.fromTextTheme(
textTheme: GoogleFonts.robotoTextTheme().copyWith(
labelLarge: _robotoTextStyle(20.sp, FontWeight.normal),
labelMedium: _robotoTextStyle(16.sp, FontWeight.normal),
Expand All @@ -60,6 +68,16 @@ class AppTextStyles extends TextTheme {
),
);

static AppTextStyles _testingTextTheme() => AppTextStyles.fromTextTheme(
textTheme: TextTheme(
labelLarge: _robotoTextStyle(20.sp, FontWeight.normal),
labelMedium: _robotoTextStyle(16.sp, FontWeight.normal),
labelSmall: _robotoTextStyle(14.sp, FontWeight.normal),
headlineMedium: _robotoTextStyle(20.sp, FontWeight.bold),
headlineLarge: _robotoTextStyle(24.sp, FontWeight.bold),
),
);

TextTheme getThemeData() => getDefaultAppStyles();
}

Expand Down
20 changes: 14 additions & 6 deletions design_system/lib/theme/custom_text_styles.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// ignore_for_file: overridden_fields

import 'dart:io';

import 'package:design_system/extensions/color_extensions.dart';
import 'package:design_system/theme/custom_colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:design_system/theme/custom_colors.dart';

const FontWeight _semiboldWeight = FontWeight.w600;

Expand Down Expand Up @@ -57,11 +59,17 @@ class CustomTextStyles extends ThemeExtension<CustomTextStyles> {
FontWeight fontWeight,
CustomColors customColors,
) =>
GoogleFonts.roboto(
fontSize: fontSize,
fontWeight: fontWeight,
color: customColors.textColor!.getShade(500),
);
Platform.environment.containsKey('FLUTTER_TEST')
? TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
color: customColors.textColor!.getShade(500),
)
: GoogleFonts.roboto(
fontSize: fontSize,
fontWeight: fontWeight,
color: customColors.textColor!.getShade(500),
);

@override
CustomTextStyles copyWith({MaterialColor? primary}) =>
Expand Down
3 changes: 2 additions & 1 deletion lib/core/common/config.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// ignore_for_file: constant_identifier_names

import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter_template/core/common/environments.dart';
Expand All @@ -13,7 +14,7 @@ interface class Config {
static const String environmentFolder = 'environments';

static const debugMode = kDebugMode;

static bool testingMode = Platform.environment.containsKey('FLUTTER_TEST');
static late String apiBaseUrl;
static late String supabaseApiKey;
static late String appDirectoryPath;
Expand Down
2 changes: 1 addition & 1 deletion lib/core/di/di_utils_module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ class UtilsDiModule {

extension _GetItDiModuleExtensions on GetIt {
void _setupModule() {
registerLazySingleton(() => AppRouter(get()));
registerLazySingleton(() => AppRouter(sessionRepository: get()));
}
}
7 changes: 4 additions & 3 deletions lib/ui/router/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ part 'app_router.gr.dart';
class AppRouter extends _$AppRouter {
@override
final List<AutoRoute> routes;
final String? initialRoute;

ReevaluateListenable authReevaluateListenable;

AppRouter(SessionRepository sessionRepository)
AppRouter({required SessionRepository sessionRepository, this.initialRoute})
: authReevaluateListenable = ReevaluateListenable.stream(
sessionRepository.status.distinct().skip(1),
),
Expand All @@ -26,7 +27,7 @@ class AppRouter extends _$AppRouter {
path: '/',
guards: [UnauthenticatedGuard(sessionRepository)],
children: [
RedirectRoute(path: '', redirectTo: 'login'),
RedirectRoute(path: '', redirectTo: initialRoute ?? 'login'),
AutoRoute(path: 'login', page: SignInRoute.page),
],
),
Expand All @@ -35,7 +36,7 @@ class AppRouter extends _$AppRouter {
guards: [AuthenticatedGuard(sessionRepository)],
path: '/',
children: [
RedirectRoute(path: '', redirectTo: 'welcome'),
RedirectRoute(path: '', redirectTo: initialRoute ?? 'welcome'),
AutoRoute(path: 'welcome', page: WelcomeRoute.page),
],
),
Expand Down
4 changes: 2 additions & 2 deletions pubspec.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ dev_dependencies:
json_serializable: 6.8.0
lints: 4.0.0
mocktail: 1.0.3
path_provider_platform_interface: 2.1.2
plugin_platform_interface: 2.1.8

## TODO remove this when dart_code_linter updates the dependencies
dependency_overrides:
Expand Down
92 changes: 92 additions & 0 deletions test/mocks/mock_app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'dart:async';

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_template/core/common/config.dart';
import 'package:flutter_template/core/common/logger.dart';
import 'package:flutter_template/core/di/di_provider.dart';
import 'package:flutter_template/core/source/common/http_service.dart';
import 'package:flutter_template/main.dart';
import 'package:flutter_template/ui/router/app_router.dart';
import 'package:hive/hive.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'services/http_service.dart';
import 'services/path_provider.dart';
import 'sources/secure_storage.dart';

class SimpleTesteableApp extends MyApp {
final Map<ApiOverrideKey, ResponseData>? apiOverrides;

final String initialRoute;
const SimpleTesteableApp._({
super.key,
this.apiOverrides,
this.initialRoute = '/',
}) : super();

static Future<SimpleTesteableApp> getUnauthenticatedApp({
Map<ApiOverrideKey, ResponseData>? apiOverrides,
String initialRoute = '/',
Map<String, String>? secureStorageData,
Map<String, Object>? sharedPreferencesData,
}) async {
await _initMockSdks(
apiOverrides: apiOverrides,
initialRoute: initialRoute,
sharedPreferenceData: secureStorageData,
secureStorageData: secureStorageData,
);
final app = SimpleTesteableApp._(
apiOverrides: apiOverrides,
initialRoute: initialRoute,
);
return app;
}
}

Future<void> _initMockSdks({
Map<ApiOverrideKey, ResponseData>? apiOverrides,
Map<String, String>? secureStorageData,
Map<String, Object>? sharedPreferenceData,
String? initialRoute,
}) async {
_initializeLocalSources(
secureStorageData: secureStorageData,
sharedPreferenceData: sharedPreferenceData,
);
await _initializeProviders(apiOverrides, initialRoute);
await Logger.init();
await Config.initialize();
Hive.init(Config.appDirectoryPath);
}

void _initializeLocalSources({
Map<String, String>? secureStorageData,
Map<String, Object>? sharedPreferenceData,
}) {
FakeFlutterSecureStorage.setInitialData(secureStorageData ?? {});
PathProviderPlatform.instance = FakePathProviderPlatform();
SharedPreferences.setMockInitialValues(sharedPreferenceData ?? {});
}

Future<void> _initializeProviders(
Map<ApiOverrideKey, ResponseData>? apiOverrides,
String? initialRoute,
) async {
await DiProvider.init();
DiProvider.instance
..allowReassignment = true
..registerLazySingleton<FlutterSecureStorage>(
() => FakeFlutterSecureStorage(),
)
..registerLazySingleton<HttpService>(
() => FakeHttpService()..mockApi(apiOverrides ?? {}),
)
..registerLazySingleton<AppRouter>(
() => AppRouter(
initialRoute: initialRoute,
sessionRepository: DiProvider.get(),
),
);
}
72 changes: 72 additions & 0 deletions test/mocks/services/http_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:dio/dio.dart';
import 'package:flutter_template/core/source/common/http_service.dart';
import 'package:mocktail/mocktail.dart';

class FakeHttpService extends Mock implements HttpServiceDio {}

typedef ResponseData = Map<String, dynamic>;
typedef ApiOverrideKey = ({String path, ApiMethod method});

enum ApiMethod { apiPost, apiGet, apiDelete, apiPut }

extension MockApiServiceExtension on FakeHttpService {
void mockApi(Map<ApiOverrideKey, ResponseData> apiOverrides) {
for (final element in apiOverrides.entries) {
switch (element.key.method) {
case ApiMethod.apiPost:
_mockPost(element.key.path, element.value);
case ApiMethod.apiGet:
_mockGet(element.key.path, element.value);
case ApiMethod.apiDelete:
_mockDelete(element.key.path, element.value);
case ApiMethod.apiPut:
_mockPut(element.key.path, element.value);
}
}
}

void _mockPost(
String path,
Map<String, dynamic> response, {
Map<String, dynamic>? parameters,
}) =>
when(() => post(path, queryParameters: parameters)).thenAnswer(
(_) async => Response(
requestOptions: RequestOptions(path: path),
data: response,
),
);
void _mockGet(
String path,
Map<String, dynamic> response, {
Map<String, dynamic>? parameters,
}) =>
when(() => get(path, queryParameters: parameters)).thenAnswer(
(_) async => Response(
requestOptions: RequestOptions(path: path),
data: response,
),
);
void _mockDelete(
String path,
Map<String, dynamic> response, {
Map<String, dynamic>? parameters,
}) =>
when(() => delete(path, queryParameters: parameters)).thenAnswer(
(_) async => Response(
requestOptions: RequestOptions(path: path),
data: response,
),
);
void _mockPut(
String path,
Map<String, dynamic> response, {
Map<String, dynamic>? parameters,
}) =>
when(() => put(path, queryParameters: parameters)).thenAnswer(
(_) async => Response(
requestOptions: RequestOptions(path: path),
data: response,
),
);
}
44 changes: 44 additions & 0 deletions test/mocks/services/path_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

const String kTemporaryPath = 'temporaryPath';
const String kApplicationSupportPath = 'applicationSupportPath';
const String kDownloadsPath = 'downloadsPath';
const String kLibraryPath = 'libraryPath';
const String kApplicationDocumentsPath = 'applicationDocumentsPath';
const String kExternalCachePath = 'externalCachePath';
const String kExternalStoragePath = 'externalStoragePath';

class FakePathProviderPlatform extends Fake
with MockPlatformInterfaceMixin
implements PathProviderPlatform {
@override
Future<String?> getTemporaryPath() async => kTemporaryPath;

@override
Future<String?> getApplicationSupportPath() async => kApplicationSupportPath;

@override
Future<String?> getLibraryPath() async => kLibraryPath;

@override
Future<String?> getApplicationDocumentsPath() async =>
kApplicationDocumentsPath;

@override
Future<String?> getExternalStoragePath() async => kExternalStoragePath;

@override
Future<List<String>?> getExternalCachePaths() async =>
<String>[kExternalCachePath];

@override
Future<List<String>?> getExternalStoragePaths({
StorageDirectory? type,
}) async =>
<String>[kExternalStoragePath];

@override
Future<String?> getDownloadsPath() async => kDownloadsPath;
}
7 changes: 7 additions & 0 deletions test/mocks/sources/secure_storage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class FakeFlutterSecureStorage extends FlutterSecureStorage {
static void setInitialData(Map<String, String> initialData) {
FlutterSecureStorage.setMockInitialValues(initialData);
}
}
Loading

0 comments on commit 5c15e70

Please sign in to comment.