mirror of
https://github.com/hanskokx/arcane_framework.git
synced 2026-08-12 06:10:55 +02:00
ebbe8d99ad
* Enhance reset method in ArcaneLogger to clear interceptors along with metadata and interfaces Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com> * Update Logging feature to replace @LoggingFeature annotation with LoggerName mixin for runtime accessibility Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com> * Bump version to 2.0.2 in pubspec.yaml and update CHANGELOG to reflect the new version Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com> * Add migration steps for replacing @LoggingFeature annotation with LoggerName mixin in CHANGELOG Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com> * Add TestLoggerWithLoggerName class to enhance logging functionality and verify LoggerName mixin behavior Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com> * Fix class declaration in migration example for LoggerName mixin Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com> --------- Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
717 lines
21 KiB
Dart
717 lines
21 KiB
Dart
import "package:arcane_framework/arcane_framework.dart";
|
|
import "package:flutter_test/flutter_test.dart";
|
|
|
|
class TestLoggingInterface extends LoggingInterface with LoggingInitialization {
|
|
TestLoggingInterface(this.name);
|
|
|
|
final String name;
|
|
int initCallCount = 0;
|
|
final List<LogEvent> events = [];
|
|
|
|
@override
|
|
Future<void> init() async {
|
|
await super.init();
|
|
initCallCount += 1;
|
|
}
|
|
|
|
@override
|
|
void log(
|
|
String message, {
|
|
Map<String, Object?>? metadata,
|
|
Level? level,
|
|
StackTrace? stackTrace,
|
|
Object? extra,
|
|
}) {
|
|
events.add(
|
|
LogEvent(
|
|
message: message,
|
|
metadata: metadata == null ? null : Map<String, Object?>.from(metadata),
|
|
level: level,
|
|
stackTrace: stackTrace,
|
|
extra: extra,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TestPassiveLoggingInterface extends LoggingInterface {
|
|
TestPassiveLoggingInterface(this.name);
|
|
|
|
final String name;
|
|
final List<LogEvent> events = [];
|
|
|
|
@override
|
|
void log(
|
|
String message, {
|
|
Map<String, Object?>? metadata,
|
|
Level? level,
|
|
StackTrace? stackTrace,
|
|
Object? extra,
|
|
}) {
|
|
events.add(
|
|
LogEvent(
|
|
message: message,
|
|
metadata: metadata == null ? null : Map<String, Object?>.from(metadata),
|
|
level: level,
|
|
stackTrace: stackTrace,
|
|
extra: extra,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TestAlternativeLoggingInterface extends LoggingInterface {
|
|
TestAlternativeLoggingInterface(this.name);
|
|
|
|
final String name;
|
|
final List<LogEvent> events = [];
|
|
|
|
@override
|
|
void log(
|
|
String message, {
|
|
Map<String, Object?>? metadata,
|
|
Level? level,
|
|
StackTrace? stackTrace,
|
|
Object? extra,
|
|
}) {
|
|
events.add(
|
|
LogEvent(
|
|
message: message,
|
|
metadata: metadata == null ? null : Map<String, Object?>.from(metadata),
|
|
level: level,
|
|
stackTrace: stackTrace,
|
|
extra: extra,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TestDerivedLoggingInterface extends TestLoggingInterface {
|
|
TestDerivedLoggingInterface(super.name);
|
|
}
|
|
|
|
class RedactingLogInterceptor implements LogInterceptor {
|
|
const RedactingLogInterceptor();
|
|
|
|
@override
|
|
LogEvent? call(
|
|
LogEvent event, {
|
|
required LogInterceptorContext context,
|
|
}) {
|
|
final Object? token = event.metadata?["token"];
|
|
if (token == null) return event;
|
|
|
|
return event.copyWith(
|
|
metadata: {
|
|
...?event.metadata,
|
|
"token": "[redacted]",
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class TestLoggerWithLoggerName extends LoggingInterface with LoggerName {
|
|
@override
|
|
String get name => "test-logger";
|
|
|
|
final List<LogEvent> events = [];
|
|
|
|
@override
|
|
void log(
|
|
String message, {
|
|
Map<String, Object?>? metadata,
|
|
Level? level,
|
|
StackTrace? stackTrace,
|
|
Object? extra,
|
|
}) {
|
|
events.add(
|
|
LogEvent(
|
|
message: message,
|
|
metadata: metadata == null ? null : Map<String, Object?>.from(metadata),
|
|
level: level,
|
|
stackTrace: stackTrace,
|
|
extra: extra,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
late TestLoggingInterface myInterface;
|
|
late LogInterceptor prefixInterceptor;
|
|
final TestLoggerWithLoggerName loggerWithLoggerName =
|
|
TestLoggerWithLoggerName();
|
|
|
|
setUp(() {
|
|
Arcane.logger.reset();
|
|
myInterface = TestLoggingInterface("primary");
|
|
prefixInterceptor = LogInterceptor(
|
|
(event, context) {
|
|
return event.copyWith(message: "[global] ${event.message}");
|
|
},
|
|
);
|
|
});
|
|
|
|
group("ArcaneLogger", () {
|
|
group("stream lifecycle", () {
|
|
test("logStream remains usable after listener cancellation", () async {
|
|
String? firstMessage;
|
|
final firstSubscription = Arcane.logger.logStream.listen((message) {
|
|
firstMessage = message;
|
|
});
|
|
|
|
Arcane.log("first");
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(firstMessage, contains("first"));
|
|
|
|
await firstSubscription.cancel();
|
|
|
|
String? secondMessage;
|
|
final secondSubscription = Arcane.logger.logStream.listen((message) {
|
|
secondMessage = message;
|
|
});
|
|
|
|
Arcane.log("second");
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(secondMessage, contains("second"));
|
|
|
|
await secondSubscription.cancel();
|
|
});
|
|
});
|
|
|
|
group("interface management", () {
|
|
test("registerInterfaces adds interfaces correctly", () async {
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
|
|
expect(
|
|
Arcane.logger.interfaces,
|
|
contains(isA<LoggingInterface>()),
|
|
);
|
|
});
|
|
|
|
test("registering an interface doesn't initialize it", () async {
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
|
|
expect(Arcane.logger.interfaces.first, isA<LoggingInterface>());
|
|
|
|
expect(myInterface.initialized, false);
|
|
expect(myInterface.initCallCount, 0);
|
|
});
|
|
|
|
test("registering an interface initializes the logger", () async {
|
|
expect(Arcane.logger.initialized, false);
|
|
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
|
|
expect(Arcane.logger.initialized, true);
|
|
});
|
|
|
|
test("interfaces can be initialized through the logger", () async {
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
|
|
expect(myInterface.initialized, false);
|
|
|
|
await Arcane.logger.initializeInterfaces();
|
|
|
|
expect(myInterface.initCallCount, 1);
|
|
});
|
|
|
|
test("non-initializable interfaces are skipped by initializeInterfaces",
|
|
() async {
|
|
final TestPassiveLoggingInterface passiveInterface =
|
|
TestPassiveLoggingInterface("passive");
|
|
|
|
await Arcane.logger.registerInterfaces([
|
|
myInterface,
|
|
passiveInterface,
|
|
]);
|
|
|
|
await Arcane.logger.initializeInterfaces();
|
|
|
|
expect(myInterface.initCallCount, 1);
|
|
|
|
Arcane.log("hello");
|
|
|
|
expect(myInterface.events.single.message, "hello");
|
|
expect(passiveInterface.events.single.message, "hello");
|
|
});
|
|
|
|
test("multiple interfaces can be registered", () async {
|
|
await Arcane.logger.registerInterfaces([
|
|
TestLoggingInterface("first"),
|
|
TestLoggingInterface("second"),
|
|
]);
|
|
|
|
expect(
|
|
Arcane.logger.interfaces,
|
|
contains(isA<TestLoggingInterface>()),
|
|
);
|
|
expect(
|
|
Arcane.logger.interfaces.length,
|
|
2,
|
|
);
|
|
});
|
|
|
|
test("global interceptors can be registered at runtime", () async {
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
|
|
Arcane.log("before");
|
|
Arcane.logger.interceptors.add(prefixInterceptor);
|
|
Arcane.log("after");
|
|
|
|
expect(myInterface.events[0].message, "before");
|
|
expect(myInterface.events[1].message, "[global] after");
|
|
});
|
|
|
|
test("interceptor collection add defaults to global", () async {
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
|
|
Arcane.log("before");
|
|
Arcane.logger.interceptors.add(prefixInterceptor);
|
|
Arcane.log("after");
|
|
|
|
expect(myInterface.events[0].message, "before");
|
|
expect(myInterface.events[1].message, "[global] after");
|
|
});
|
|
|
|
test("interface interceptors can be registered at runtime", () async {
|
|
final LogInterceptor dropForPrimary = LogInterceptor(
|
|
(event, context) {
|
|
if ((context.interface as TestLoggingInterface).name == "primary") {
|
|
return null;
|
|
}
|
|
|
|
return event;
|
|
},
|
|
);
|
|
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
|
|
Arcane.log("before");
|
|
Arcane.logger.interceptors.add(
|
|
dropForPrimary,
|
|
matcher: (LoggingInterface interface) =>
|
|
interface is TestLoggingInterface,
|
|
);
|
|
Arcane.log("blocked");
|
|
|
|
expect(
|
|
myInterface.events.map((LogEvent event) => event.message),
|
|
["before"],
|
|
);
|
|
});
|
|
|
|
test("type-scoped interceptors only apply to matching interfaces",
|
|
() async {
|
|
final TestAlternativeLoggingInterface alternativeInterface =
|
|
TestAlternativeLoggingInterface("alternative");
|
|
|
|
await Arcane.logger.registerInterfaces([
|
|
myInterface,
|
|
alternativeInterface,
|
|
]);
|
|
|
|
Arcane.logger.interceptors.add(
|
|
LogInterceptor((event, context) {
|
|
return event.copyWith(message: "[typed] ${event.message}");
|
|
}),
|
|
matcher: (LoggingInterface interface) =>
|
|
interface is TestLoggingInterface,
|
|
);
|
|
|
|
Arcane.log("typed");
|
|
|
|
expect(myInterface.events.last.message, "[typed] typed");
|
|
expect(alternativeInterface.events.last.message, "typed");
|
|
});
|
|
|
|
test("matcher-based type-scoped interceptors can include subtypes",
|
|
() async {
|
|
final TestDerivedLoggingInterface derivedInterface =
|
|
TestDerivedLoggingInterface("derived");
|
|
final TestAlternativeLoggingInterface alternativeInterface =
|
|
TestAlternativeLoggingInterface("alternative");
|
|
|
|
await Arcane.logger.registerInterfaces([
|
|
myInterface,
|
|
derivedInterface,
|
|
alternativeInterface,
|
|
]);
|
|
|
|
Arcane.logger.interceptors.add(
|
|
LogInterceptor((event, context) {
|
|
return event.copyWith(message: "[family] ${event.message}");
|
|
}),
|
|
matcher: (LoggingInterface interface) =>
|
|
interface is TestLoggingInterface,
|
|
);
|
|
|
|
Arcane.log("typed");
|
|
|
|
expect(myInterface.events.last.message, "[family] typed");
|
|
expect(derivedInterface.events.last.message, "[family] typed");
|
|
expect(alternativeInterface.events.last.message, "typed");
|
|
});
|
|
|
|
test(
|
|
"registering a type-scoped interceptor before matching interface exists stores it for future registrations",
|
|
() async {
|
|
final TestLoggingInterface secondary =
|
|
TestLoggingInterface("secondary");
|
|
|
|
Arcane.logger.interceptors.add(
|
|
LogInterceptor((event, context) {
|
|
return event.copyWith(message: "[future] ${event.message}");
|
|
}),
|
|
matcher: (LoggingInterface interface) =>
|
|
interface is TestLoggingInterface,
|
|
);
|
|
|
|
await Arcane.logger.registerInterface(secondary);
|
|
Arcane.log("hello");
|
|
|
|
expect(secondary.events.single.message, "[future] hello");
|
|
});
|
|
});
|
|
|
|
group("persistent metadata", () {
|
|
test("addPersistentMetadata adds metadata correctly", () {
|
|
Arcane.logger.addPersistentMetadata({"test": "value"});
|
|
expect(Arcane.logger.additionalMetadata["test"], equals("value"));
|
|
});
|
|
|
|
test("removePersistentMetadata removes specific key", () {
|
|
Arcane.logger.addPersistentMetadata({"test": "value", "keep": "this"});
|
|
Arcane.logger.removePersistentMetadata("test");
|
|
expect(Arcane.logger.additionalMetadata.containsKey("test"), false);
|
|
expect(Arcane.logger.additionalMetadata["keep"], equals("this"));
|
|
});
|
|
|
|
test("clearPersistentMetadata removes all metadata", () {
|
|
Arcane.logger
|
|
.addPersistentMetadata({"test": "value", "another": "value"});
|
|
Arcane.logger.clearPersistentMetadata();
|
|
expect(Arcane.logger.additionalMetadata.isEmpty, true);
|
|
});
|
|
});
|
|
|
|
group("logging messages", () {
|
|
const String logMessage = "Test";
|
|
|
|
setUp(() async {
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
});
|
|
|
|
test("logging a basic message works", () async {
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events.single.message, logMessage);
|
|
});
|
|
|
|
test("logging at a different level works", () async {
|
|
Arcane.log(
|
|
logMessage,
|
|
level: Level.info,
|
|
);
|
|
|
|
expect(myInterface.events.last.level, Level.info);
|
|
|
|
Arcane.log(
|
|
logMessage,
|
|
level: Level.warning,
|
|
);
|
|
|
|
expect(myInterface.events.last.level, Level.warning);
|
|
});
|
|
|
|
test("logging a stacktrace works", () async {
|
|
final stackTrace = StackTrace.current;
|
|
Arcane.log(logMessage, stackTrace: stackTrace);
|
|
|
|
expect(myInterface.events.single.stackTrace, stackTrace);
|
|
});
|
|
|
|
test("logging an extra object works", () async {
|
|
const bool extraObject = true;
|
|
Arcane.log(
|
|
logMessage,
|
|
extra: extraObject,
|
|
);
|
|
|
|
expect(myInterface.events.single.extra, extraObject);
|
|
});
|
|
|
|
test("logging metadata works", () async {
|
|
final Map<String, String> metadata = {"test": "value"};
|
|
Arcane.log(
|
|
logMessage,
|
|
metadata: metadata,
|
|
);
|
|
|
|
expect(myInterface.events.single.metadata?["test"], "value");
|
|
expect(
|
|
myInterface.events.single.metadata?.containsKey("timestamp"),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("global interceptors run in registration order", () async {
|
|
Arcane.logger.interceptors.addAll([
|
|
LogInterceptor((event, context) {
|
|
expect(context.interface, same(myInterface));
|
|
return event.copyWith(message: "${event.message}:first");
|
|
}),
|
|
LogInterceptor((event, context) {
|
|
expect(context.interface, same(myInterface));
|
|
return event.copyWith(message: "${event.message}:second");
|
|
}),
|
|
]);
|
|
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events.single.message, "Test:first:second");
|
|
});
|
|
|
|
test("global interceptors can drop events for all interfaces", () async {
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
Arcane.logger.interceptors.add(
|
|
LogInterceptor((event, context) => null),
|
|
);
|
|
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events, isEmpty);
|
|
});
|
|
|
|
test("interceptor collection remove defaults to global", () async {
|
|
Arcane.logger.interceptors.add(prefixInterceptor);
|
|
Arcane.logger.interceptors.remove(prefixInterceptor);
|
|
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events.single.message, logMessage);
|
|
});
|
|
|
|
test("custom interceptor classes can implement LogInterceptor", () async {
|
|
Arcane.logger.interceptors.add(const RedactingLogInterceptor());
|
|
|
|
Arcane.log(
|
|
logMessage,
|
|
metadata: {"token": "secret-token"},
|
|
);
|
|
|
|
expect(
|
|
myInterface.events.single.metadata?["token"],
|
|
"[redacted]",
|
|
);
|
|
});
|
|
|
|
test("interface interceptors can drop events per destination", () async {
|
|
final TestLoggingInterface secondaryInterface =
|
|
TestLoggingInterface("secondary");
|
|
final LogInterceptor allowPrimaryOnly = LogInterceptor(
|
|
(event, context) {
|
|
final String name =
|
|
(context.interface as TestLoggingInterface).name;
|
|
return name == "primary" ? event : null;
|
|
},
|
|
);
|
|
|
|
Arcane.logger.interceptors.add(
|
|
allowPrimaryOnly,
|
|
matcher: (LoggingInterface interface) =>
|
|
interface is TestLoggingInterface,
|
|
);
|
|
await Arcane.logger.registerInterface(
|
|
secondaryInterface,
|
|
);
|
|
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events.single.message, logMessage);
|
|
expect(secondaryInterface.events, isEmpty);
|
|
});
|
|
|
|
test("interceptor collection remove with matcher removes scoped entries",
|
|
() async {
|
|
final TestLoggingInterface secondaryInterface =
|
|
TestLoggingInterface("secondary");
|
|
final LogInterceptor scopedInterceptor =
|
|
LogInterceptor((event, context) {
|
|
return event.copyWith(message: "[scoped] ${event.message}");
|
|
});
|
|
bool scopedMatcher(LoggingInterface interface) =>
|
|
interface is TestLoggingInterface;
|
|
|
|
Arcane.logger.interceptors.add(
|
|
scopedInterceptor,
|
|
matcher: scopedMatcher,
|
|
);
|
|
|
|
await Arcane.logger.registerInterface(secondaryInterface);
|
|
Arcane.logger.interceptors.remove(
|
|
scopedInterceptor,
|
|
matcher: scopedMatcher,
|
|
);
|
|
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events.single.message, logMessage);
|
|
expect(secondaryInterface.events.single.message, logMessage);
|
|
});
|
|
|
|
test("interface interceptors receive the current interface", () async {
|
|
final TestLoggingInterface secondaryInterface =
|
|
TestLoggingInterface("secondary");
|
|
|
|
Arcane.logger.interceptors.add(
|
|
LogInterceptor((event, context) {
|
|
final TestLoggingInterface currentInterface =
|
|
context.interface! as TestLoggingInterface;
|
|
return event.copyWith(
|
|
metadata: {
|
|
...?event.metadata,
|
|
"target": currentInterface.name,
|
|
},
|
|
);
|
|
}),
|
|
matcher: (LoggingInterface interface) =>
|
|
interface is TestLoggingInterface,
|
|
);
|
|
await Arcane.logger.registerInterface(
|
|
secondaryInterface,
|
|
);
|
|
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events.single.metadata?["target"], "primary");
|
|
expect(
|
|
secondaryInterface.events.single.metadata?["target"],
|
|
"secondary",
|
|
);
|
|
});
|
|
|
|
test("interface interceptors cannot mutate sibling interface events",
|
|
() async {
|
|
final TestLoggingInterface secondaryInterface =
|
|
TestLoggingInterface("secondary");
|
|
|
|
Arcane.logger.interceptors.add(
|
|
LogInterceptor((event, context) {
|
|
event.metadata?["mutatedBy"] =
|
|
(context.interface as TestLoggingInterface).name;
|
|
return event;
|
|
}),
|
|
matcher: (LoggingInterface interface) =>
|
|
interface is TestLoggingInterface,
|
|
);
|
|
await Arcane.logger.registerInterface(
|
|
secondaryInterface,
|
|
);
|
|
|
|
Arcane.log(
|
|
logMessage,
|
|
metadata: {"test": "value"},
|
|
);
|
|
|
|
expect(myInterface.events.single.metadata?["mutatedBy"], "primary");
|
|
expect(
|
|
secondaryInterface.events.single.metadata?["mutatedBy"],
|
|
"secondary",
|
|
);
|
|
});
|
|
|
|
test("unregistering an interface clears registration interceptors",
|
|
() async {
|
|
final TestLoggingInterface secondaryInterface =
|
|
TestLoggingInterface("secondary");
|
|
|
|
await Arcane.logger.unregisterInterface(myInterface);
|
|
myInterface = TestLoggingInterface("primary-with-drop");
|
|
await Arcane.logger.registerInterface(
|
|
myInterface,
|
|
interceptors: [
|
|
LogInterceptor((event, context) => null),
|
|
],
|
|
);
|
|
|
|
await Arcane.logger.unregisterInterface(myInterface);
|
|
await Arcane.logger.registerInterface(secondaryInterface);
|
|
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events, isEmpty);
|
|
expect(secondaryInterface.events.single.message, logMessage);
|
|
});
|
|
|
|
test("reset clears global interceptors", () async {
|
|
Arcane.logger.interceptors.add(prefixInterceptor);
|
|
Arcane.logger.reset();
|
|
await Arcane.logger.registerInterface(myInterface);
|
|
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events.single.message, logMessage);
|
|
});
|
|
|
|
test("global and type-scoped duplicate registrations are additive",
|
|
() async {
|
|
final LogInterceptor duplicateInterceptor = LogInterceptor(
|
|
(event, context) {
|
|
final int count = (event.metadata?["count"] as int?) ?? 0;
|
|
return event.copyWith(
|
|
metadata: {
|
|
...?event.metadata,
|
|
"count": count + 1,
|
|
},
|
|
);
|
|
},
|
|
);
|
|
|
|
Arcane.logger
|
|
..interceptors.add(duplicateInterceptor)
|
|
..interceptors.add(
|
|
duplicateInterceptor,
|
|
matcher: (LoggingInterface interface) =>
|
|
interface is TestLoggingInterface,
|
|
);
|
|
|
|
Arcane.log(
|
|
logMessage,
|
|
metadata: {
|
|
"count": 0,
|
|
},
|
|
);
|
|
|
|
expect(myInterface.events.single.metadata?["count"], 2);
|
|
});
|
|
|
|
test(
|
|
"interceptor collection clear removes global and scoped interceptors",
|
|
() async {
|
|
Arcane.logger.interceptors.add(prefixInterceptor);
|
|
Arcane.logger.interceptors.add(
|
|
LogInterceptor((event, context) {
|
|
return event.copyWith(message: "[scoped] ${event.message}");
|
|
}),
|
|
matcher: (LoggingInterface interface) =>
|
|
interface is TestLoggingInterface,
|
|
);
|
|
|
|
Arcane.logger.interceptors.clear();
|
|
Arcane.log(logMessage);
|
|
|
|
expect(myInterface.events.single.message, logMessage);
|
|
});
|
|
});
|
|
});
|
|
|
|
test("LoggerName mixin exposes name at runtime", () {
|
|
expect(loggerWithLoggerName.name, "test-logger");
|
|
|
|
loggerWithLoggerName.log("Test message");
|
|
expect(loggerWithLoggerName.events.length, 1);
|
|
expect(loggerWithLoggerName.events.first.message, "Test message");
|
|
});
|
|
}
|