Integrate logging system into Flutter app with service registration and testing setup
This commit is contained in:
@@ -1,6 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttery/fluttery.dart';
|
||||||
|
import 'package:fluttery/logger/logger.dart';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
// Ensures that the Flutter engine and widget binding
|
||||||
|
// are initialized before using async services or plugins
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// any services
|
||||||
|
App.registerDefaultServices();
|
||||||
|
|
||||||
|
final logger = App.service<Logger>();
|
||||||
|
logger.debug("[MAIN] Registered all default services");
|
||||||
|
|
||||||
void main() {
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
fluttery:
|
||||||
|
path: ../fluttery
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
|||||||
@@ -1,5 +1,61 @@
|
|||||||
/// A Calculator.
|
library;
|
||||||
class Calculator {
|
|
||||||
/// Returns [value] plus 1.
|
import 'package:fluttery/logger/logger.dart';
|
||||||
int addOne(int value) => value + 1;
|
import 'package:fluttery/src/logger/logger_impl.dart';
|
||||||
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
|
||||||
|
/// A class to manage services.
|
||||||
|
class App {
|
||||||
|
static final _AppService _appService = _AppService();
|
||||||
|
|
||||||
|
/// Registers a service with a factory function to instantiate the implementation.
|
||||||
|
///
|
||||||
|
/// This ensures that the implementation is created when the service is requested.
|
||||||
|
///
|
||||||
|
/// `implFactory` - A factory method to create the service implementation.
|
||||||
|
static void registerService<T extends Service>(T Function() implFactory) {
|
||||||
|
_appService.registerSingleton<T>(implFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the registered service.
|
||||||
|
///
|
||||||
|
/// Returns an instance of the registered service.
|
||||||
|
static T service<T extends Service>() {
|
||||||
|
return _appService.resolve<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers the default services required by the application.
|
||||||
|
static void registerDefaultServices() {
|
||||||
|
registerService<Logger>(() => LoggerImpl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Abstract class to represent a service.
|
||||||
|
abstract class Service {}
|
||||||
|
|
||||||
|
/// Internal class to manage the registration and resolution of services.
|
||||||
|
class _AppService {
|
||||||
|
static _AppService? _singleton;
|
||||||
|
|
||||||
|
static final KiwiContainer _kiwi = KiwiContainer();
|
||||||
|
|
||||||
|
/// Factory constructor to ensure singleton instance of _AppService.
|
||||||
|
factory _AppService() => _singleton ??= _AppService._();
|
||||||
|
|
||||||
|
/// Private constructor.
|
||||||
|
_AppService._();
|
||||||
|
|
||||||
|
/// Registers a singleton service with a factory function to create the instance.
|
||||||
|
///
|
||||||
|
/// `serviceFactory` - A factory method to create the service implementation.
|
||||||
|
void registerSingleton<T extends Service>(T Function() serviceFactory) {
|
||||||
|
_kiwi.registerFactory<T>((c) => serviceFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves and retrieves the registered service.
|
||||||
|
///
|
||||||
|
/// Returns an instance of the registered service.
|
||||||
|
T resolve<T extends Service>() {
|
||||||
|
return _kiwi.resolve<T>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
finlog_app/fluttery/lib/logger/logger.dart
Normal file
33
finlog_app/fluttery/lib/logger/logger.dart
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:fluttery/fluttery.dart';
|
||||||
|
import 'package:logging/logging.dart' as lib;
|
||||||
|
|
||||||
|
/// Abstract class for logging service.
|
||||||
|
/// Provides methods for different log levels and configuration.
|
||||||
|
abstract class Logger extends Service {
|
||||||
|
/// Logs an informational message.
|
||||||
|
///
|
||||||
|
/// [message] is the information to log.
|
||||||
|
void info(String message);
|
||||||
|
|
||||||
|
/// Logs a warning message.
|
||||||
|
///
|
||||||
|
/// [message] is the warning to log.
|
||||||
|
void warning(String message);
|
||||||
|
|
||||||
|
/// Logs an error message with optional error and stack trace.
|
||||||
|
///
|
||||||
|
/// [message] is the error message to log.
|
||||||
|
/// [error] is the optional error object associated with this log entry.
|
||||||
|
/// [stackTrace] is the optional stack trace associated with this log entry.
|
||||||
|
void error(String message, [Object? error, StackTrace? stackTrace]);
|
||||||
|
|
||||||
|
/// Logs a debug message.
|
||||||
|
///
|
||||||
|
/// [message] is the debug message to log.
|
||||||
|
void debug(String message);
|
||||||
|
|
||||||
|
/// Sets the log level for the logger.
|
||||||
|
///
|
||||||
|
/// [level] is the new log level to set.
|
||||||
|
void setLogLevel(lib.Level level);
|
||||||
|
}
|
||||||
57
finlog_app/fluttery/lib/src/logger/logger_impl.dart
Normal file
57
finlog_app/fluttery/lib/src/logger/logger_impl.dart
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttery/logger/logger.dart';
|
||||||
|
import 'package:logging/logging.dart' as lib;
|
||||||
|
|
||||||
|
// ignore_for_file: avoid_print
|
||||||
|
class LoggerImpl implements Logger {
|
||||||
|
final lib.Logger _logger;
|
||||||
|
|
||||||
|
// coverage:ignore-start
|
||||||
|
/// Constructor
|
||||||
|
LoggerImpl() : _logger = lib.Logger("Logger") {
|
||||||
|
_logger.onRecord.listen((lib.LogRecord record) {
|
||||||
|
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||||
|
if (record.error != null) {
|
||||||
|
print('Error: ${record.error}');
|
||||||
|
}
|
||||||
|
if (record.stackTrace != null) {
|
||||||
|
print('Stack Trace: ${record.stackTrace}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// coverage:ignore-end
|
||||||
|
@visibleForTesting
|
||||||
|
factory LoggerImpl.forTest(lib.Logger logger) {
|
||||||
|
final instance = LoggerImpl._internal(logger);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private internal constructor
|
||||||
|
LoggerImpl._internal(this._logger);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void info(String message) {
|
||||||
|
_logger.info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void warning(String message) {
|
||||||
|
_logger.warning(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void error(String message, [Object? error, StackTrace? stackTrace]) {
|
||||||
|
_logger.severe(message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debug(String message) {
|
||||||
|
_logger.fine(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setLogLevel(lib.Level level) {
|
||||||
|
_logger.level = level;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
kiwi: ^5.0.1
|
||||||
|
logging: ^1.3.0
|
||||||
|
mocktail: ^1.0.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -1,12 +1,37 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'package:fluttery/fluttery.dart';
|
import 'package:fluttery/fluttery.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:fluttery/logger/logger.dart';
|
||||||
|
import 'package:fluttery/src/logger/logger_impl.dart';
|
||||||
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
|
||||||
|
import 'mocks/mocks.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('adds one to input values', () {
|
group('App Service Tests', () {
|
||||||
final calculator = Calculator();
|
// Clear the singleton state before each test to ensure isolation
|
||||||
expect(calculator.addOne(2), 3);
|
setUp(() {
|
||||||
expect(calculator.addOne(-7), -6);
|
// KiwiContainer provides a clear method to remove all registered services
|
||||||
expect(calculator.addOne(0), 1);
|
KiwiContainer().clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should register and resolve a custom service', () {
|
||||||
|
// Register a mock logger service
|
||||||
|
final mockLogger = MockLogger();
|
||||||
|
App.registerService<Logger>(() => mockLogger);
|
||||||
|
|
||||||
|
// Resolve the service and check if it's the same instance
|
||||||
|
final resolvedLogger = App.service<Logger>();
|
||||||
|
expect(resolvedLogger, isA<MockLogger>());
|
||||||
|
expect(resolvedLogger, same(mockLogger));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should register and resolve default services', () {
|
||||||
|
// Register default services
|
||||||
|
App.registerDefaultServices();
|
||||||
|
|
||||||
|
// Resolve the Logger service and check if it's an instance of LoggerImpl
|
||||||
|
final logger = App.service<Logger>();
|
||||||
|
expect(logger, isA<LoggerImpl>());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
69
finlog_app/fluttery/test/logger/logger_test.dart
Normal file
69
finlog_app/fluttery/test/logger/logger_test.dart
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:logging/logging.dart' as lib;
|
||||||
|
import 'package:fluttery/src/logger/logger_impl.dart';
|
||||||
|
|
||||||
|
// Mock class for the lib.Logger
|
||||||
|
class MockLibLogger extends Mock implements lib.Logger {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final mockLibLogger = MockLibLogger();
|
||||||
|
|
||||||
|
group('LoggerImpl', () {
|
||||||
|
late LoggerImpl loggerImpl;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
loggerImpl = LoggerImpl.forTest(mockLibLogger);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('info method logs an info message', () {
|
||||||
|
loggerImpl.info('Info message');
|
||||||
|
|
||||||
|
// Verify that the info method was called on the mock logger with the correct message
|
||||||
|
verify(() => mockLibLogger.info('Info message')).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('warning method logs a warning message', () {
|
||||||
|
loggerImpl.warning('Warning message');
|
||||||
|
|
||||||
|
// Verify that the warning method was called on the mock logger with the correct message
|
||||||
|
verify(() => mockLibLogger.warning('Warning message')).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error method logs an error message with optional parameters', () {
|
||||||
|
final exception = Exception('Test exception');
|
||||||
|
final stackTrace = StackTrace.current;
|
||||||
|
|
||||||
|
loggerImpl.error('Error message', exception, stackTrace);
|
||||||
|
|
||||||
|
// Verify that the severe method was called on the mock logger with the correct parameters
|
||||||
|
verify(
|
||||||
|
() => mockLibLogger.severe('Error message', exception, stackTrace),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('debug method logs a debug message', () {
|
||||||
|
loggerImpl.debug('Debug message');
|
||||||
|
|
||||||
|
// Verify that the fine method was called on the mock logger with the correct message
|
||||||
|
verify(() => mockLibLogger.fine('Debug message')).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setLogLevel method sets the logger level', () {
|
||||||
|
// This is to capture the change in the logger level
|
||||||
|
var capturedLevel = lib.Level.INFO;
|
||||||
|
|
||||||
|
// Capture the setter call
|
||||||
|
when(() => mockLibLogger.level = any()).thenAnswer((invocation) {
|
||||||
|
capturedLevel = invocation.positionalArguments.first as lib.Level;
|
||||||
|
return capturedLevel;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the log level
|
||||||
|
loggerImpl.setLogLevel(lib.Level.WARNING);
|
||||||
|
|
||||||
|
// Verify that the logger level is set to the expected level
|
||||||
|
expect(capturedLevel, lib.Level.WARNING);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
4
finlog_app/fluttery/test/mocks/mocks.dart
Normal file
4
finlog_app/fluttery/test/mocks/mocks.dart
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import 'package:fluttery/logger/logger.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
class MockLogger extends Mock implements Logger {}
|
||||||
Reference in New Issue
Block a user