diff --git a/packages/url_launcher/url_launcher/test/url_launcher_test.dart b/packages/url_launcher/url_launcher/test/url_launcher_test.dart index 2481e5a0ee63..861b18e06625 100644 --- a/packages/url_launcher/url_launcher/test/url_launcher_test.dart +++ b/packages/url_launcher/url_launcher/test/url_launcher_test.dart @@ -13,7 +13,6 @@ import 'package:flutter/services.dart' show PlatformException; void main() { final MockUrlLauncher mock = MockUrlLauncher(); - when(mock.isMock).thenReturn(true); UrlLauncherPlatform.instance = mock; test('closeWebView default behavior', () async { @@ -208,4 +207,4 @@ void main() { }); } -class MockUrlLauncher extends Mock implements UrlLauncherPlatform {} +class MockUrlLauncher extends Mock with MockPlatformInterface implements UrlLauncherPlatform {} diff --git a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md index 18d373d183f2..f12ad149e05a 100644 --- a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.0.4 + +* Remove `@visibleForTesting` API `isMock`. +* Introduces `MockPlatformInterface` and `PlatformInterface` classes. + ## 1.0.3 * Minor DartDoc changes and add a lint for missing DartDocs. diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart b/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart index 64eeec7cae2d..cfad2f87bf90 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart @@ -8,6 +8,62 @@ import 'package:meta/meta.dart' show required, visibleForTesting; import 'method_channel_url_launcher.dart'; +/// Base class for platform interfaces. +/// +/// Provides a static helper method for ensuring that platform interfaces are +/// implemented using `extends` instead of `implements`. +// TODO(amirh): Extract common platform interface logic. +// https://github.com/flutter/flutter/issues/43368 +abstract class PlatformInterface { + /// Pass a private, class-specific `const Object()` as the `token`. + PlatformInterface({@required Object token}) : _instanceToken = token; + + final Object _instanceToken; + + /// Ensures that the platform instance has a token that matches the + /// provided token and throws [AssertionError] if not. + /// + /// This is used to ensure that implementers are using `extends` rather than + /// `implements`. + /// + /// Subclasses of [MockPlatformInterface] are assumed to be valid in debug + /// builds. + /// + /// This is implemented as a static method so that it cannot be overridden + /// with `noSuchMethod`. + static void verifyToken(PlatformInterface instance, Object token) { + if (identical(instance._instanceToken, MockPlatformInterface._token)) { + bool assertionsEnabled = false; + assert(() { + assertionsEnabled = true; + return true; + }()); + if (!assertionsEnabled) { + throw AssertionError( + '`MockPlatformInterface` is not intended for use in release builds.'); + } + } + if (!identical(token, instance._instanceToken)) { + throw AssertionError( + 'Platform interfaces must not be implemented with `implements`'); + } + } +} + +/// A [PlatformInterface] mixin that can be combined with mockito's `Mock`. +/// +/// It passes the [PlatformInterface.verifyToken] check even though it isn't +/// using `extends`. +/// +/// This class is intended for use in tests only. +@visibleForTesting +abstract class MockPlatformInterface implements PlatformInterface { + static const Object _token = Object(); + + @override + Object get _instanceToken => _token; +} + /// The interface that implementations of url_launcher must implement. /// /// Platform implementations should extend this class rather than implement it as `url_launcher` @@ -15,17 +71,13 @@ import 'method_channel_url_launcher.dart'; /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [UrlLauncherPlatform] methods. -abstract class UrlLauncherPlatform { - /// Only mock implementations should set this to true. - /// - /// Mockito mocks are implementing this class with `implements` which is forbidden for anything - /// other than mocks (see class docs). This property provides a backdoor for mockito mocks to - /// skip the verification that the class isn't implemented with `implements`. - @visibleForTesting - bool get isMock => false; +abstract class UrlLauncherPlatform extends PlatformInterface { + UrlLauncherPlatform() : super(token: _token); static UrlLauncherPlatform _instance = MethodChannelUrlLauncher(); + static const Object _token = Object(); + /// The default instance of [UrlLauncherPlatform] to use. /// /// Defaults to [MethodChannelUrlLauncher]. @@ -33,17 +85,8 @@ abstract class UrlLauncherPlatform { /// Platform-specific plugins should set this with their own platform-specific /// class that extends [UrlLauncherPlatform] when they register themselves. - // TODO(amirh): Extract common platform interface logic. - // https://github.com/flutter/flutter/issues/43368 static set instance(UrlLauncherPlatform instance) { - if (!instance.isMock) { - try { - instance._verifyProvidesDefaultImplementations(); - } on NoSuchMethodError catch (_) { - throw AssertionError( - 'Platform interfaces must not be implemented with `implements`'); - } - } + PlatformInterface.verifyToken(instance, _token); _instance = instance; } @@ -72,12 +115,4 @@ abstract class UrlLauncherPlatform { Future closeWebView() { throw UnimplementedError('closeWebView() has not been implemented.'); } - - // This method makes sure that UrlLauncher isn't implemented with `implements`. - // - // See class doc for more details on why implementing this class is forbidden. - // - // This private method is called by the instance setter, which fails if the class is - // implemented with `implements`. - void _verifyProvidesDefaultImplementations() {} } diff --git a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml index 3f7aa4832c41..9793c2b6f3cc 100644 --- a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml +++ b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.0.3 +version: 1.0.4 dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart b/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart index 1d6b0c9f3f8c..6c9334f6c524 100644 --- a/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart +++ b/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart @@ -25,9 +25,8 @@ void main() { }); test('Can be mocked with `implements`', () { - final ImplementsUrlLauncherPlatform mock = - ImplementsUrlLauncherPlatform(); - when(mock.isMock).thenReturn(true); + final UrlLauncherPlatform mock = + ImplementsUrlLauncherPlatformUsingMockPlatformInterface(); UrlLauncherPlatform.instance = mock; }); @@ -283,4 +282,8 @@ void main() { class ImplementsUrlLauncherPlatform extends Mock implements UrlLauncherPlatform {} +class ImplementsUrlLauncherPlatformUsingMockPlatformInterface extends Mock + with MockPlatformInterface + implements UrlLauncherPlatform {} + class ExtendsUrlLauncherPlatform extends UrlLauncherPlatform {}