[pigeon] Added more dart unit tests, added commented out test for the encoder fix. (#329)

diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index e611fa4..0125754 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## 0.2.1
 
 * Java: Fixed issue where multiple async HostApis can generate multiple Result interfaces.
+* Dart: Made it so you can specify the BinaryMessenger of the generated APIs.
 
 ## 0.2.0
 
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index 41c1dcb..56fdc22 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -28,6 +28,15 @@
   bool first = true;
   indent.write('class ${api.name} ');
   indent.scoped('{', '}', () {
+    indent.format('''
+/// Constructor for [${api.name}].  The [binaryMessenger] named argument is
+/// available for dependency injection.  If it is left null, the default
+/// BinaryMessenger will be used which routes to the host platform.
+${api.name}({BinaryMessenger$nullTag binaryMessenger}) : _binaryMessenger = binaryMessenger;
+
+final BinaryMessenger$nullTag _binaryMessenger;
+''');
+
     for (final Method func in api.methods) {
       if (!first) {
         indent.writeln('');
@@ -51,10 +60,10 @@
         }
         final String channelName = makeChannelName(api, func);
         indent.writeln(
-            'const BasicMessageChannel<Object$nullTag> channel = BasicMessageChannel<Object$nullTag>(');
+            'final BasicMessageChannel<Object$nullTag> channel = BasicMessageChannel<Object$nullTag>(');
         indent.nest(2, () {
           indent.writeln(
-            '\'$channelName\', StandardMessageCodec());',
+            '\'$channelName\', const StandardMessageCodec(), binaryMessenger: _binaryMessenger);',
           );
         });
         final String returnStatement = func.returnType == 'void'
diff --git a/packages/pigeon/pigeons/flutter_unittests.dart b/packages/pigeon/pigeons/flutter_unittests.dart
new file mode 100644
index 0000000..acc28b8
--- /dev/null
+++ b/packages/pigeon/pigeons/flutter_unittests.dart
@@ -0,0 +1,30 @@
+// Copyright 2020 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:pigeon/pigeon.dart';
+
+class SearchRequest {
+  String? query;
+}
+
+class SearchReply {
+  String? result;
+  String? error;
+}
+
+class SearchRequests {
+  // ignore: always_specify_types
+  List? requests;
+}
+
+class SearchReplies {
+  // ignore: always_specify_types
+  List? replies;
+}
+
+@HostApi()
+abstract class Api {
+  SearchReply search(SearchRequest request);
+  SearchReplies doSearches(SearchRequests request);
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
index ef41241..5154847 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
@@ -1,6 +1,6 @@
-// Autogenerated from Pigeon (v0.1.21), do not edit directly.
+// Autogenerated from Pigeon (v0.2.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
-// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types
 // @dart = 2.12
 import 'dart:async';
 import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
@@ -28,121 +28,62 @@
 
 class SearchRequest {
   String? query;
-  int? anInt;
-  bool? aBool;
 
   Object encode() {
     final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
     pigeonMap['query'] = query;
-    pigeonMap['anInt'] = anInt;
-    pigeonMap['aBool'] = aBool;
     return pigeonMap;
   }
 
   static SearchRequest decode(Object message) {
     final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
-    return SearchRequest()
-      ..query = pigeonMap['query'] as String?
-      ..anInt = pigeonMap['anInt'] as int?
-      ..aBool = pigeonMap['aBool'] as bool?;
+    return SearchRequest()..query = pigeonMap['query'] as String?;
   }
 }
 
-class Nested {
-  SearchRequest? request;
+class SearchReplies {
+  List<Object?>? replies;
 
   Object encode() {
     final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
-    pigeonMap['request'] = request == null ? null : request!.encode();
+    pigeonMap['replies'] = replies;
     return pigeonMap;
   }
 
-  static Nested decode(Object message) {
+  static SearchReplies decode(Object message) {
     final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
-    return Nested()
-      ..request = pigeonMap['request'] != null
-          ? SearchRequest.decode(pigeonMap['request']!)
-          : null;
+    return SearchReplies()..replies = pigeonMap['replies'] as List<Object?>?;
   }
 }
 
-abstract class FlutterSearchApi {
-  SearchReply search(SearchRequest arg);
-  static void setup(FlutterSearchApi? api) {
-    {
-      const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-          'dev.flutter.pigeon.FlutterSearchApi.search', StandardMessageCodec());
-      if (api == null) {
-        channel.setMessageHandler(null);
-      } else {
-        channel.setMessageHandler((Object? message) async {
-          assert(message != null,
-              'Argument for dev.flutter.pigeon.FlutterSearchApi.search was null. Expected SearchRequest.');
-          final SearchRequest input = SearchRequest.decode(message!);
-          final SearchReply output = api.search(input);
-          return output.encode();
-        });
-      }
-    }
-  }
-}
+class SearchRequests {
+  List<Object?>? requests;
 
-class NestedApi {
-  Future<SearchReply> search(Nested arg) async {
-    final Object encoded = arg.encode();
-    const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.NestedApi.search', StandardMessageCodec());
-    final Map<Object?, Object?>? replyMap =
-        await channel.send(encoded) as Map<Object?, Object?>?;
-    if (replyMap == null) {
-      throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
-        details: null,
-      );
-    } else if (replyMap['error'] != null) {
-      final Map<Object?, Object?> error =
-          replyMap['error']! as Map<Object?, Object?>;
-      throw PlatformException(
-        code: error['code']! as String,
-        message: error['message'] as String?,
-        details: error['details'],
-      );
-    } else {
-      return SearchReply.decode(replyMap['result']!);
-    }
+  Object encode() {
+    final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+    pigeonMap['requests'] = requests;
+    return pigeonMap;
+  }
+
+  static SearchRequests decode(Object message) {
+    final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
+    return SearchRequests()..requests = pigeonMap['requests'] as List<Object?>?;
   }
 }
 
 class Api {
-  Future<void> initialize() async {
-    const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.Api.initialize', StandardMessageCodec());
-    final Map<Object?, Object?>? replyMap =
-        await channel.send(null) as Map<Object?, Object?>?;
-    if (replyMap == null) {
-      throw PlatformException(
-        code: 'channel-error',
-        message: 'Unable to establish connection on channel.',
-        details: null,
-      );
-    } else if (replyMap['error'] != null) {
-      final Map<Object?, Object?> error =
-          replyMap['error']! as Map<Object?, Object?>;
-      throw PlatformException(
-        code: error['code']! as String,
-        message: error['message'] as String?,
-        details: error['details'],
-      );
-    } else {
-      // noop
-    }
-  }
+  /// Constructor for [Api].  The [binaryMessenger] named argument is
+  /// available for dependency injection.  If it is left null, the default
+  /// BinaryMessenger will be used which routes to the host platform.
+  Api({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger;
+
+  final BinaryMessenger? _binaryMessenger;
 
   Future<SearchReply> search(SearchRequest arg) async {
     final Object encoded = arg.encode();
-    const BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
-        'dev.flutter.pigeon.Api.search', StandardMessageCodec());
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.Api.search', const StandardMessageCodec(),
+        binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
         await channel.send(encoded) as Map<Object?, Object?>?;
     if (replyMap == null) {
@@ -153,9 +94,9 @@
       );
     } else if (replyMap['error'] != null) {
       final Map<Object?, Object?> error =
-          replyMap['error']! as Map<Object?, Object?>;
+          (replyMap['error'] as Map<Object?, Object?>?)!;
       throw PlatformException(
-        code: error['code']! as String,
+        code: (error['code'] as String?)!,
         message: error['message'] as String?,
         details: error['details'],
       );
@@ -163,4 +104,30 @@
       return SearchReply.decode(replyMap['result']!);
     }
   }
+
+  Future<SearchReplies> doSearches(SearchRequests arg) async {
+    final Object encoded = arg.encode();
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.Api.doSearches', const StandardMessageCodec(),
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(encoded) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return SearchReplies.decode(replyMap['result']!);
+    }
+  }
 }
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/pubspec.yaml b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/pubspec.yaml
index 32058de..9b21234 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/pubspec.yaml
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/pubspec.yaml
@@ -10,8 +10,10 @@
     sdk: flutter
 
 dev_dependencies:
+  build_runner: ^1.11.0
   flutter_test:
     sdk: flutter
+  mockito: ^5.0.4
 
 flutter:
   uses-material-design: true
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart
index 523127a..c407fd9 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart
@@ -2,9 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
+import 'package:flutter/services.dart';
 import 'package:flutter_unit_tests/null_safe_pigeon.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'null_safe_test.mocks.dart';
 
+@GenerateMocks(<Type>[BinaryMessenger])
 void main() {
   test('with values filled', () {
     final SearchReply reply = SearchReply()
@@ -25,4 +32,43 @@
     expect(reply.result, decoded.result);
     expect(reply.error, decoded.error);
   });
+
+  test('send/receive', () async {
+    final SearchRequest request = SearchRequest()..query = 'hey';
+    final SearchReply reply = SearchReply()..result = 'ho';
+    final BinaryMessenger mockMessenger = MockBinaryMessenger();
+    const MessageCodec<Object?> codec = StandardMessageCodec();
+    final Completer<ByteData?> completer = Completer<ByteData?>();
+    completer.complete(
+        codec.encodeMessage(<String, Object>{'result': reply.encode()}));
+    final Future<ByteData?> sendResult = completer.future;
+    when(mockMessenger.send('dev.flutter.pigeon.Api.search', any))
+        .thenAnswer((Invocation realInvocation) => sendResult);
+    final Api api = Api(binaryMessenger: mockMessenger);
+    final SearchReply readReply = await api.search(request);
+    expect(readReply, isNotNull);
+    expect(reply.result, readReply.result);
+  });
+
+  // TODO(gaaclarke): This test is a companion for the fix to https://github.com/flutter/flutter/issues/80538
+  // test('send/receive list classes', () async {
+  //   final SearchRequest request = SearchRequest()
+  //       ..query = 'hey';
+  //   final SearchReply reply = SearchReply()
+  //       ..result = 'ho';
+  //   final SearchRequests requests = SearchRequests()
+  //       ..requests = <SearchRequest>[request];
+  //   final SearchReplies replies = SearchReplies()
+  //       ..replies = <SearchReply>[reply];
+  //   final BinaryMessenger mockMessenger = MockBinaryMessenger();
+  //   const MessageCodec<Object?> codec = StandardMessageCodec();
+  //   final Completer<ByteData?> completer = Completer<ByteData?>();
+  //   completer.complete(codec.encodeMessage(<String, Object>{'result' : replies.encode()}));
+  //   final Future<ByteData?> sendResult = completer.future;
+  //   when(mockMessenger.send('dev.flutter.pigeon.Api.search', any)).thenAnswer((Invocation realInvocation) => sendResult);
+  //   final Api api = Api(binaryMessenger: mockMessenger);
+  //   final SearchReplies readReplies = await api.doSearches(requests);
+  //   expect(readReplies, isNotNull);
+  //   expect(reply.result, (readReplies.replies![0] as SearchReply?)!.result);
+  // });
 }
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.mocks.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.mocks.dart
new file mode 100644
index 0000000..5a326b5
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.mocks.dart
@@ -0,0 +1,55 @@
+// Mocks generated by Mockito 5.0.4 from annotations
+// in flutter_unit_tests/test/null_safe_test.dart.
+// Do not manually edit this file.
+
+import 'dart:async' as _i3;
+import 'dart:typed_data' as _i4;
+import 'dart:ui' as _i5;
+
+import 'package:flutter/src/services/binary_messenger.dart' as _i2;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: comment_references
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: always_specify_types
+
+/// A class which mocks [BinaryMessenger].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockBinaryMessenger extends _i1.Mock implements _i2.BinaryMessenger {
+  MockBinaryMessenger() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i3.Future<void> handlePlatformMessage(String? channel, _i4.ByteData? data,
+          _i5.PlatformMessageResponseCallback? callback) =>
+      (super.noSuchMethod(
+          Invocation.method(#handlePlatformMessage, [channel, data, callback]),
+          returnValue: Future<void>.value(null),
+          returnValueForMissingStub:
+              Future<dynamic>.value()) as _i3.Future<void>);
+  @override
+  _i3.Future<_i4.ByteData?>? send(String? channel, _i4.ByteData? message) =>
+      (super.noSuchMethod(Invocation.method(#send, [channel, message]))
+          as _i3.Future<_i4.ByteData?>?);
+  @override
+  void setMessageHandler(String? channel, _i2.MessageHandler? handler) => super
+      .noSuchMethod(Invocation.method(#setMessageHandler, [channel, handler]),
+          returnValueForMissingStub: null);
+  @override
+  bool checkMessageHandler(String? channel, _i2.MessageHandler? handler) =>
+      (super.noSuchMethod(
+          Invocation.method(#checkMessageHandler, [channel, handler]),
+          returnValue: false) as bool);
+  @override
+  void setMockMessageHandler(String? channel, _i2.MessageHandler? handler) =>
+      super.noSuchMethod(
+          Invocation.method(#setMockMessageHandler, [channel, handler]),
+          returnValueForMissingStub: null);
+  @override
+  bool checkMockMessageHandler(String? channel, _i2.MessageHandler? handler) =>
+      (super.noSuchMethod(
+          Invocation.method(#checkMockMessageHandler, [channel, handler]),
+          returnValue: false) as bool);
+}
diff --git a/packages/pigeon/run_tests.sh b/packages/pigeon/run_tests.sh
index d76d5f8..82f09bb 100755
--- a/packages/pigeon/run_tests.sh
+++ b/packages/pigeon/run_tests.sh
@@ -192,7 +192,7 @@
 run_flutter_unittests() {
   pushd $PWD
   pub run pigeon \
-    --input pigeons/message.dart \
+    --input pigeons/flutter_unittests.dart \
     --dart_out platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
   cd platform_tests/flutter_null_safe_unit_tests
   flutter pub get