From 26bea2216715fdeed0d3f852573acd7d5c6219fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Arias=20Va=CC=81zquez?= Date: Sat, 23 Mar 2024 13:13:18 +0100 Subject: [PATCH] chore: Add tests and mock for testing the AssetManager implementation The build.yaml was added to be able to create the AssetManager mock. See more here: https://stackoverflow.com/a/68275812 --- .../package_info_plus/example/build.yaml | 13 + .../package_info_plus_web_test.dart | 109 ++++- .../package_info_plus_web_test.mocks.dart | 384 ++++++++++++++---- .../lib/src/package_info_plus_web.dart | 8 +- 4 files changed, 434 insertions(+), 80 deletions(-) create mode 100644 packages/package_info_plus/package_info_plus/example/build.yaml diff --git a/packages/package_info_plus/package_info_plus/example/build.yaml b/packages/package_info_plus/package_info_plus/example/build.yaml new file mode 100644 index 0000000000..f0808f935f --- /dev/null +++ b/packages/package_info_plus/package_info_plus/example/build.yaml @@ -0,0 +1,13 @@ +targets: + $default: + sources: + - $package$ + - lib/$lib$ + - lib/**.dart + - test/**.dart + - integration_test/**.dart + builders: + mockito|mockBuilder: + generate_for: + - test/**.dart + - integration_test/**.dart \ No newline at end of file diff --git a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart index 1bad594242..e01210bc47 100644 --- a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart +++ b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart @@ -2,6 +2,7 @@ library package_info_plus_web_test; import 'dart:convert'; +import 'dart:ui_web' as ui_web; import 'package:clock/clock.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -14,7 +15,7 @@ import 'package:package_info_plus/src/package_info_plus_web.dart'; import 'package_info_plus_test.dart' as common_tests; import 'package_info_plus_web_test.mocks.dart'; -@GenerateMocks([http.Client]) +@GenerateMocks([http.Client, ui_web.AssetManager]) void main() { common_tests.main(); @@ -30,17 +31,28 @@ void main() { 'build_signature': '', }; + // ignore: constant_identifier_names + const VERSION_2_JSON = { + 'app_name': 'package_info_example', + 'build_number': '2', + 'package_name': 'io.flutter.plugins.packageinfoexample', + 'version': '2.0', + 'installerStore': null, + 'build_signature': '', + }; + late PackageInfoPlusWebPlugin plugin; late MockClient client; - - setUp(() { - client = MockClient(); - plugin = PackageInfoPlusWebPlugin(client); - }); + late MockAssetManager assetManagerMock; group( 'Package Info Web', () { + setUp(() { + client = MockClient(); + plugin = PackageInfoPlusWebPlugin(client); + }); + testWidgets( 'Get correct values when response status is 200', (tester) async { @@ -207,4 +219,89 @@ void main() { }); }, ); + + group('Package Info Web (using MockAssetManager)', () { + setUp(() { + client = MockClient(); + assetManagerMock = MockAssetManager(); + plugin = PackageInfoPlusWebPlugin(client, assetManagerMock); + }); + + testWidgets( + 'Get correct values when using the AssetManager baseUrl', + (tester) async { + const String baseUrl = 'https://an.example.com/using-asset-manager/'; + const String assetsDir = 'assets'; + final DateTime now = DateTime.now(); + final Clock fakeClock = Clock(() => now); + + when(assetManagerMock.assetsDir).thenReturn(assetsDir); + when(assetManagerMock.getAssetUrl('')) + .thenReturn('$baseUrl$assetsDir/'); + + await withClock(fakeClock, () async { + final int cache = now.millisecondsSinceEpoch; + + when(client.get( + Uri.parse('${baseUrl}version.json?cachebuster=$cache'), + )).thenAnswer( + (_) => Future.value( + http.Response(jsonEncode(VERSION_JSON), 200), + ), + ); + + final versionMap = await plugin.getAll(); + + expect(versionMap.appName, VERSION_JSON['app_name']); + expect(versionMap.version, VERSION_JSON['version']); + expect(versionMap.buildNumber, VERSION_JSON['build_number']); + expect(versionMap.packageName, VERSION_JSON['package_name']); + expect(versionMap.buildSignature, VERSION_JSON['build_signature']); + }); + }, + ); + + testWidgets( + 'Has preference for the custom base URL over the other 2 locations', + (tester) async { + const String customBaseUrl = 'https://www.example.com/with-path/'; + const String managerBaseUrl = 'https://www.asset-manager.com/path/'; + const String assetsDir = 'assets'; + final DateTime now = DateTime.now(); + final Clock fakeClock = Clock(() => now); + + when(assetManagerMock.assetsDir).thenReturn(assetsDir); + when(assetManagerMock.getAssetUrl('')) + .thenReturn('$managerBaseUrl$assetsDir/'); + + await withClock(fakeClock, () async { + final int cache = now.millisecondsSinceEpoch; + + when(client.get( + Uri.parse('${customBaseUrl}version.json?cachebuster=$cache'), + )).thenAnswer( + (_) => Future.value( + http.Response(jsonEncode(VERSION_JSON), 200), + ), + ); + + when(client.get( + Uri.parse('${managerBaseUrl}version.json?cachebuster=$cache'), + )).thenAnswer( + (_) => Future.value( + http.Response(jsonEncode(VERSION_2_JSON), 200), + ), + ); + + final versionMap = await plugin.getAll(baseUrl: customBaseUrl); + + expect(versionMap.appName, VERSION_JSON['app_name']); + expect(versionMap.version, VERSION_JSON['version']); + expect(versionMap.buildNumber, VERSION_JSON['build_number']); + expect(versionMap.packageName, VERSION_JSON['package_name']); + expect(versionMap.buildSignature, VERSION_JSON['build_signature']); + }); + }, + ); + }); } diff --git a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.mocks.dart b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.mocks.dart index 33a76e432c..bd10e30c8e 100644 --- a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.mocks.dart +++ b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.mocks.dart @@ -1,108 +1,350 @@ -// Mocks generated by Mockito 5.0.14 from annotations -// in package_info_plus_web/test/package_info_plus_web_test_disabled.dart. +// Mocks generated by Mockito 5.4.4 from annotations +// in package_info_plus_example/integration_test/package_info_plus_web_test.dart. // Do not manually edit this file. -// ignore_for_file: camel_case_types, unnecessary_overrides +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; +import 'dart:convert' as _i4; +import 'dart:typed_data' as _i6; +import 'dart:ui_web' as _i7; -import 'dart:async' as i5; -import 'dart:convert' as i6; -import 'dart:typed_data' as i7; - -import 'package:http/src/base_request.dart' as i8; -import 'package:http/src/client.dart' as i4; -import 'package:http/src/response.dart' as i2; -import 'package:http/src/streamed_response.dart' as i3; -import 'package:mockito/mockito.dart' as i1; +import 'package:http/http.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeResponse_0 extends _i1.SmartFake implements _i2.Response { + _FakeResponse_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeResponse_0 extends i1.Fake implements i2.Response {} +class _FakeStreamedResponse_1 extends _i1.SmartFake + implements _i2.StreamedResponse { + _FakeStreamedResponse_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} -class _FakeStreamedResponse_1 extends i1.Fake implements i3.StreamedResponse {} +class _FakeObject_2 extends _i1.SmartFake implements Object { + _FakeObject_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} /// A class which mocks [Client]. /// /// See the documentation for Mockito's code generation for more information. -class MockClient extends i1.Mock implements i4.Client { +class MockClient extends _i1.Mock implements _i2.Client { MockClient() { - i1.throwOnMissingStub(this); + _i1.throwOnMissingStub(this); } @override - i5.Future head(Uri? url, {Map? headers}) => - (super.noSuchMethod(Invocation.method(#head, [url], {#headers: headers}), - returnValue: Future.value(_FakeResponse_0())) - as i5.Future); + _i3.Future<_i2.Response> head( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + )), + ) as _i3.Future<_i2.Response>); + @override - i5.Future get(Uri? url, {Map? headers}) => - (super.noSuchMethod(Invocation.method(#get, [url], {#headers: headers}), - returnValue: Future.value(_FakeResponse_0())) - as i5.Future); + _i3.Future<_i2.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i3.Future<_i2.Response>); + @override - i5.Future post(Uri? url, - {Map? headers, - Object? body, - i6.Encoding? encoding}) => + _i3.Future<_i2.Response> post( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => (super.noSuchMethod( - Invocation.method(#post, [url], - {#headers: headers, #body: body, #encoding: encoding}), - returnValue: Future.value(_FakeResponse_0())) - as i5.Future); + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + @override - i5.Future put(Uri? url, - {Map? headers, - Object? body, - i6.Encoding? encoding}) => + _i3.Future<_i2.Response> put( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => (super.noSuchMethod( - Invocation.method(#put, [url], - {#headers: headers, #body: body, #encoding: encoding}), - returnValue: Future.value(_FakeResponse_0())) - as i5.Future); + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + @override - i5.Future patch(Uri? url, - {Map? headers, - Object? body, - i6.Encoding? encoding}) => + _i3.Future<_i2.Response> patch( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => (super.noSuchMethod( - Invocation.method(#patch, [url], - {#headers: headers, #body: body, #encoding: encoding}), - returnValue: Future.value(_FakeResponse_0())) - as i5.Future); + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> delete( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + @override - i5.Future delete(Uri? url, - {Map? headers, - Object? body, - i6.Encoding? encoding}) => + _i3.Future read( + Uri? url, { + Map? headers, + }) => (super.noSuchMethod( - Invocation.method(#delete, [url], - {#headers: headers, #body: body, #encoding: encoding}), - returnValue: Future.value(_FakeResponse_0())) - as i5.Future); + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future.value(_i5.dummyValue( + this, + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + )), + ) as _i3.Future); + @override - i5.Future read(Uri? url, {Map? headers}) => - (super.noSuchMethod(Invocation.method(#read, [url], {#headers: headers}), - returnValue: Future.value('')) as i5.Future); + _i3.Future<_i6.Uint8List> readBytes( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #readBytes, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i3.Future<_i6.Uint8List>); + @override - i5.Future readBytes(Uri? url, {Map? headers}) => + _i3.Future<_i2.StreamedResponse> send(_i2.BaseRequest? request) => (super.noSuchMethod( - Invocation.method(#readBytes, [url], {#headers: headers}), - returnValue: Future.value(i7.Uint8List(0))) - as i5.Future); + Invocation.method( + #send, + [request], + ), + returnValue: + _i3.Future<_i2.StreamedResponse>.value(_FakeStreamedResponse_1( + this, + Invocation.method( + #send, + [request], + ), + )), + ) as _i3.Future<_i2.StreamedResponse>); + @override - i5.Future send(i8.BaseRequest? request) => - (super.noSuchMethod(Invocation.method(#send, [request]), - returnValue: - Future.value(_FakeStreamedResponse_1())) - as i5.Future); + void close() => super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [AssetManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAssetManager extends _i1.Mock implements _i7.AssetManager { + MockAssetManager() { + _i1.throwOnMissingStub(this); + } + + @override + String get assetsDir => (super.noSuchMethod( + Invocation.getter(#assetsDir), + returnValue: _i5.dummyValue( + this, + Invocation.getter(#assetsDir), + ), + ) as String); + @override - void close() => super.noSuchMethod(Invocation.method(#close, []), - returnValueForMissingStub: null); + String getAssetUrl(String? asset) => (super.noSuchMethod( + Invocation.method( + #getAssetUrl, + [asset], + ), + returnValue: _i5.dummyValue( + this, + Invocation.method( + #getAssetUrl, + [asset], + ), + ), + ) as String); + + @override + _i3.Future loadAsset(String? asset) => (super.noSuchMethod( + Invocation.method( + #loadAsset, + [asset], + ), + returnValue: _i3.Future.value(_FakeObject_2( + this, + Invocation.method( + #loadAsset, + [asset], + ), + )), + ) as _i3.Future); + @override - String toString() => super.toString(); + _i3.Future<_i6.ByteData> load(String? asset) => (super.noSuchMethod( + Invocation.method( + #load, + [asset], + ), + returnValue: _i3.Future<_i6.ByteData>.value(_i6.ByteData(0)), + ) as _i3.Future<_i6.ByteData>); } diff --git a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart index 167295e6e6..0be152e430 100644 --- a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart +++ b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart @@ -13,9 +13,11 @@ import 'package:web/web.dart' as web; /// This class implements the `package:package_info_plus` functionality for the web. class PackageInfoPlusWebPlugin extends PackageInfoPlatform { final Client? _client; + final AssetManager _assetManager; - /// Create plugin with http client. - PackageInfoPlusWebPlugin([this._client]); + /// Create plugin with http client and asset manager for testing purposes. + PackageInfoPlusWebPlugin([this._client, AssetManager? assetManagerMock]) + : _assetManager = assetManagerMock ?? assetManager; /// Registers this class as the default instance of [PackageInfoPlatform]. static void registerWith(Registrar registrar) { @@ -55,7 +57,7 @@ class PackageInfoPlusWebPlugin extends PackageInfoPlatform { final int cacheBuster = clock.now().millisecondsSinceEpoch; final Map versionMap = await _getVersionMap(baseUrl, cacheBuster) ?? - await _getVersionMap(assetManager.baseUrl, cacheBuster) ?? + await _getVersionMap(_assetManager.baseUrl, cacheBuster) ?? await _getVersionMap(web.window.document.baseURI, cacheBuster) ?? {};