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

Added unit test cases #125

Merged
merged 12 commits into from
Nov 13, 2024
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ build/
launch.json

#Coverage
coverage/
coverage/

# FVM Version Cache
.fvm/
7 changes: 5 additions & 2 deletions packages/core/lib/analytics_pigeon.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import 'package:segment_analytics/analytics_platform_interface.dart';
import 'package:flutter/services.dart';

Expand All @@ -7,11 +8,13 @@ import 'native_context.dart';
class AnalyticsPlatformImpl extends AnalyticsPlatform {
static const EventChannel _eChannel =
EventChannel('analytics/deep_link_events');
final NativeContextApi _api = NativeContextApi();
NativeContextApi api;

AnalyticsPlatformImpl({NativeContextApi? api}) : api = api ?? NativeContextApi();

@override
Future<NativeContext> getContext({bool collectDeviceId = false}) {
return _api.getContext(collectDeviceId);
return api.getContext(collectDeviceId);
}

@override
Expand Down
4 changes: 4 additions & 0 deletions packages/core/lib/flush_policies/count_flush_policy.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:segment_analytics/event.dart';
import 'package:segment_analytics/flush_policies/flush_policy.dart';

Expand All @@ -7,6 +8,9 @@ class CountFlushPolicy extends FlushPolicy {

CountFlushPolicy(this._flushAt, {int? count}) : _count = count ?? 0;

@visibleForTesting
int get count => _count;

@override
void start() {
_count = 0;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/lib/plugins/segment_destination.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ class SegmentDestination extends DestinationPlugin with Flushable {
String? _apiHost;

SegmentDestination() : super(segmentDestinationKey) {
_queuePlugin = QueueFlushingPlugin(_sendEvents);
_queuePlugin = QueueFlushingPlugin(sendEvents);
}

Future _sendEvents(List<RawEvent> events) async {
Future sendEvents(List<RawEvent> events) async {
if (events.isEmpty) {
return;
}
Expand Down
6 changes: 4 additions & 2 deletions packages/core/lib/utils/lifecycle/fgbg.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import 'package:segment_analytics/utils/lifecycle/lifecycle.dart';
import 'package:flutter_fgbg/flutter_fgbg.dart';

class FGBGLifecycle extends LifeCycle {
final _stream = FGBGEvents.instance.stream;
final Stream<FGBGType> stream;

FGBGLifecycle(this.stream);

@override
StreamSubscription<AppStatus> listen(void Function(AppStatus event)? onData,
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
return _stream
return stream
.map((event) => (event == FGBGType.foreground)
? AppStatus.foreground
: AppStatus.background)
Expand Down
8 changes: 5 additions & 3 deletions packages/core/lib/utils/lifecycle/lifecycle.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter_fgbg/flutter_fgbg.dart';
import 'package:segment_analytics/utils/lifecycle/fgbg.dart';
import 'package:segment_analytics/utils/lifecycle/widget_observer.dart';
import 'package:flutter/foundation.dart';
Expand All @@ -9,16 +10,17 @@ enum AppStatus { foreground, background }

abstract class LifeCycle extends Stream<AppStatus> {}

LifeCycle _getLifecycleStream() {
@visibleForTesting
LifeCycle getLifecycleStream() {
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
// For iOS and Android we will use the FgBg Lifecycle listener as it reports directly from native level
// ignoring native popups
return FGBGLifecycle();
return FGBGLifecycle(FGBGEvents.instance.stream);
} else {
// For Web and Desktop we use the WidgetObserver implementation directly from Flutter
// TBF Flutter doesn't have a very reliable background signal for those platforms
return WidgetObserverLifecycle();
}
}

final LifeCycle lifecycle = _getLifecycleStream();
final LifeCycle lifecycle = getLifecycleStream();
1 change: 1 addition & 0 deletions packages/core/lib/utils/store/io.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// coverage:ignore-file
import 'dart:async';
import 'dart:convert';
import 'dart:io';
Expand Down
3 changes: 2 additions & 1 deletion packages/core/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ dev_dependencies:
build_runner: ^2.4.7
flutter_test:
sdk: flutter
fake_async: ^1.0.0
flutter_lints: ^4.0.0
json_serializable: ^6.8.0
pigeon: ^7.2.1
mockito: ^5.4.4
mockito: ^5.3.2

flutter:
plugin:
Expand Down
33 changes: 33 additions & 0 deletions packages/core/test/analytics_pigeon_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// test/analytics_platform_impl_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:segment_analytics/analytics_pigeon.dart';

import 'package:segment_analytics/native_context.dart';
import 'mocks/mocks.mocks.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

group('AnalyticsPlatformImpl Tests', () {
late AnalyticsPlatformImpl analyticsPlatform;
late MockNativeContextApi mockNativeContextApi;

setUp(() {
mockNativeContextApi = MockNativeContextApi();
analyticsPlatform = AnalyticsPlatformImpl();
analyticsPlatform.api = mockNativeContextApi;
});

test('getContext returns NativeContext', () async {
final nativeContext = NativeContext();
when(mockNativeContextApi.getContext(any))
.thenAnswer((_) async => nativeContext);

final result = await analyticsPlatform.getContext(collectDeviceId: true);

expect(result, isA<NativeContext>());
verify(mockNativeContextApi.getContext(true)).called(1);
});
});
}
116 changes: 92 additions & 24 deletions packages/core/test/analytics_test.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import 'package:segment_analytics/analytics.dart';
import 'package:segment_analytics/analytics_platform_interface.dart';
import 'package:segment_analytics/client.dart';
import 'package:segment_analytics/event.dart';
import 'package:segment_analytics/flush_policies/count_flush_policy.dart';
import 'package:segment_analytics/flush_policies/flush_policy.dart';
import 'package:segment_analytics/logger.dart';
import 'package:segment_analytics/plugins/event_logger.dart';
import 'package:segment_analytics/state.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'mocks/mocks.dart';
import 'mocks/mocks.mocks.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();
Expand All @@ -21,55 +26,118 @@ void main() {
];

group("analytics", () {

setUp(() {
late Analytics analytics;
late MockHTTPClient httpClient;
setUp(() async {
AnalyticsPlatform.instance = MockPlatform();

// Prevents spamming the test console. Eventually logging info will be behind a debug flag so this won't be needed
LogFactory.logger = Mocks.logTarget();

SharedPreferences.setMockInitialValues({});
});

test(
"it fetches settings but does not fire track event when not tracking lifecycle events",
() async {
final httpClient = Mocks.httpClient();
httpClient = Mocks.httpClient();
when(httpClient.settingsFor(writeKey))
.thenAnswer((_) => Future.value(SegmentAPISettings({})));
when(httpClient.startBatchUpload(writeKey, batch))
.thenAnswer((_) => Future.value(true));

Analytics analytics = Analytics(
analytics = Analytics(
Configuration("123",
trackApplicationLifecycleEvents: false,
appStateStream: () => Mocks.streamSubscription()),
token: "abcdef12345"),
Mocks.store(),
httpClient: (_) => httpClient);
await analytics.init();
});

test(
"it fetches settings but does not fire track event when not tracking lifecycle events",
() async {

verify(httpClient.settingsFor(writeKey));
verifyNever(httpClient.startBatchUpload(writeKey, batch));
});
test(
"it fetches settings and fires track event when tracking lifecycle events",
() async {
final httpClient = Mocks.httpClient();
when(httpClient.settingsFor(writeKey))
.thenAnswer((_) => Future.value(SegmentAPISettings({})));
when(httpClient.startBatchUpload(writeKey, batch))
.thenAnswer((_) => Future.value(true));

Analytics analytics = Analytics(
Configuration("123",
trackApplicationLifecycleEvents: true,
appStateStream: () => Mocks.streamSubscription()),
Mocks.store(),
httpClient: (_) => httpClient);
await analytics.init();

verify(httpClient.settingsFor(writeKey));
verifyNever(httpClient.startBatchUpload(writeKey, batch));
});

test('it analytics track should be callable', () {
analytics.track("test track");
});
test('it analytics screen should be callable', () {
analytics.screen("test screem");
});
test('it analytics identify should be callable', () {
analytics.identify();
});
test('it analytics group should be callable', () {
analytics.group("test group");
});
test('it analytics alias should be callable', () {
analytics.alias("test alias");
});
test('it analytics cleanup should be callable', () {
analytics.cleanup();
});
test('it analytics reset should be callable', () {
analytics.reset();
});
test('it analytics addFlushPolicy should be callable', () {
List<FlushPolicy> policies = [];
policies.add(CountFlushPolicy(5));
analytics.addFlushPolicy(policies);
});
test('it analytics getFlushPolicies should be callable', () {
analytics.getFlushPolicies();
});
test('it analytics removeFlushPolicy should be callable', () {
List<FlushPolicy> policies = [];
policies.add(CountFlushPolicy(5));
analytics.removeFlushPolicy(policies);
});
test('it analytics removePlugin should be callable', () {
analytics.addPlugin(EventLogger(), settings: {"event":"Track Event"});
});
test('it analytics removePlugin should be callable', () {
analytics.removePlugin(EventLogger());
});
test('it analytics onContextLoaded should be callable', () {
analytics.onContextLoaded((p0) { });
});
test('it analytics onPluginLoaded should be callable', () {
analytics.onPluginLoaded((p0) { });
});

test("Test analytics platform getContext", () {
AnalyticsPlatform analyticsPlatform = MockAnalyticsPlatform();

expect(
() async => await analyticsPlatform.getContext(),
throwsA(isA<UnimplementedError>()),
);
});
test("Test analytics platform linkStream", () {
AnalyticsPlatform analyticsPlatform = MockAnalyticsPlatform();

expect(
() async => analyticsPlatform.linkStream,
throwsA(isA<UnimplementedError>()),
);
});

test("it createClient", () async {
Analytics analytics = createClient(Configuration("123",
debug: false,
trackApplicationLifecycleEvents: true,
trackDeeplinks: true,
token: "abcdef12345")
);
expect(analytics, isA<Analytics>());
});
});
}

class MockAnalyticsPlatform extends AnalyticsPlatform { }
43 changes: 43 additions & 0 deletions packages/core/test/client_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'dart:async';

import 'package:segment_analytics/client.dart';


void main() {
late ScreenObserver screenObserver;
late Stream<String> screenStream;
late List<String> events;

setUp(() {
screenObserver = ScreenObserver();
screenStream = screenObserver.screenStream;
events = [];
screenStream.listen((event) {
events.add(event);
});
});

test('didPop adds previous route name to stream', () {
final route = MaterialPageRoute(settings: const RouteSettings(name: '/new'), builder: (_) => Container());
final previousRoute = MaterialPageRoute(settings: const RouteSettings(name: '/old'), builder: (_) => Container());
screenObserver.didPop(route, previousRoute);
});

test('didPush adds new route name to stream', () {
final route = MaterialPageRoute(settings: const RouteSettings(name: '/new'), builder: (_) => Container());
screenObserver.didPush(route, null);
});

test('didRemove adds route name to stream', () {
final route = MaterialPageRoute(settings: const RouteSettings(name: '/remove'), builder: (_) => Container());
screenObserver.didRemove(route, null);
});

test('didReplace adds new route name to stream', () {
final oldRoute = MaterialPageRoute(settings: const RouteSettings(name: '/old'), builder: (_) => Container());
final newRoute = MaterialPageRoute(settings: const RouteSettings(name: '/new'), builder: (_) => Container());
screenObserver.didReplace(newRoute: newRoute, oldRoute: oldRoute);
});
}
Loading
Loading