Skip to content

Commit

Permalink
Added unit test cases (#125)
Browse files Browse the repository at this point in the history
* Add first unit test to project

* Add new unit test

* Add new unit test to increase coverage

* Update library test and fix issue reported by 'analyze'

* Fixed errors after merging with main.

* Fixed flutter analyze warnings

* Updated flush_policy_test.dart

---------

Co-authored-by: Edson Amaya <[email protected]>
Co-authored-by: Shane L. Duvall <[email protected]>
Co-authored-by: Rishabh Jain <[email protected]>
Co-authored-by: Michael Grosse Huelsewiesche <[email protected]>
  • Loading branch information
5 people authored Nov 13, 2024
1 parent 3eedee4 commit 1fb1570
Show file tree
Hide file tree
Showing 35 changed files with 3,769 additions and 70 deletions.
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

0 comments on commit 1fb1570

Please sign in to comment.