[flutter_tools] move service extensions off of deprecated vm service (#55012)

diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
index e856412..26f1dbd 100644
--- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
@@ -34,6 +34,7 @@
 import '../reporting/reporting.dart';
 import '../resident_runner.dart';
 import '../run_hot.dart';
+import '../vmservice.dart';
 import '../web/chrome.dart';
 import '../web/compile.dart';
 import '../web/web_device.dart';
@@ -195,9 +196,10 @@
   @override
   Future<void> debugDumpApp() async {
     try {
-      await _vmService?.callServiceExtension(
-        'ext.flutter.debugDumpApp',
-      );
+      await _vmService
+        ?.flutterDebugDumpApp(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -206,9 +208,10 @@
   @override
   Future<void> debugDumpRenderTree() async {
     try {
-      await _vmService?.callServiceExtension(
-        'ext.flutter.debugDumpRenderTree',
-      );
+      await _vmService
+        ?.flutterDebugDumpRenderTree(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -217,9 +220,10 @@
   @override
   Future<void> debugDumpLayerTree() async {
     try {
-      await _vmService?.callServiceExtension(
-        'ext.flutter.debugDumpLayerTree',
-      );
+      await _vmService
+        ?.flutterDebugDumpLayerTree(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -228,8 +232,10 @@
   @override
   Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
     try {
-      await _vmService?.callServiceExtension(
-          'ext.flutter.debugDumpSemanticsTreeInTraversalOrder');
+      await _vmService
+        ?.flutterDebugDumpSemanticsTreeInTraversalOrder(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -238,14 +244,16 @@
   @override
   Future<void> debugTogglePlatform() async {
     try {
-      final vmservice.Response response = await _vmService
-          ?.callServiceExtension('ext.flutter.platformOverride');
-      final String currentPlatform = response.json['value'] as String;
+      final String currentPlatform = await _vmService
+        ?.flutterPlatformOverride(
+          isolateId: null,
+        );
       final String platform = nextPlatform(currentPlatform, featureFlags);
-      await _vmService?.callServiceExtension('ext.flutter.platformOverride',
-          args: <String, Object>{
-            'value': platform,
-          });
+      await _vmService
+        ?.flutterPlatformOverride(
+            platform: platform,
+            isolateId: null,
+          );
       globals.printStatus('Switched operating system to $platform');
     } on vmservice.RPCError {
       return;
@@ -261,8 +269,10 @@
   @override
   Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
     try {
-      await _vmService?.callServiceExtension(
-          'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder');
+      await _vmService
+        ?.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -271,16 +281,10 @@
   @override
   Future<void> debugToggleDebugPaintSizeEnabled() async {
     try {
-      final vmservice.Response response =
-          await _vmService?.callServiceExtension(
-        'ext.flutter.debugPaint',
-      );
-      await _vmService?.callServiceExtension(
-        'ext.flutter.debugPaint',
-        args: <dynamic, dynamic>{
-          'enabled': !(response.json['enabled'] == 'true')
-        },
-      );
+      await _vmService
+        ?.flutterToggleDebugPaintSizeEnabled(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -289,16 +293,10 @@
   @override
   Future<void> debugToggleDebugCheckElevationsEnabled() async {
     try {
-      final vmservice.Response response =
-          await _vmService?.callServiceExtension(
-        'ext.flutter.debugCheckElevationsEnabled',
-      );
-      await _vmService?.callServiceExtension(
-        'ext.flutter.debugCheckElevationsEnabled',
-        args: <dynamic, dynamic>{
-          'enabled': !(response.json['enabled'] == 'true')
-        },
-      );
+      await _vmService
+        ?.flutterToggleDebugCheckElevationsEnabled(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -307,14 +305,10 @@
   @override
   Future<void> debugTogglePerformanceOverlayOverride() async {
     try {
-      final vmservice.Response response = await _vmService
-          ?.callServiceExtension('ext.flutter.showPerformanceOverlay');
-      await _vmService?.callServiceExtension(
-        'ext.flutter.showPerformanceOverlay',
-        args: <dynamic, dynamic>{
-          'enabled': !(response.json['enabled'] == 'true')
-        },
-      );
+      await _vmService
+        ?.flutterTogglePerformanceOverlayOverride(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -323,14 +317,10 @@
   @override
   Future<void> debugToggleWidgetInspector() async {
     try {
-      final vmservice.Response response = await _vmService
-          ?.callServiceExtension('ext.flutter.debugToggleWidgetInspector');
-      await _vmService?.callServiceExtension(
-        'ext.flutter.debugToggleWidgetInspector',
-        args: <dynamic, dynamic>{
-          'enabled': !(response.json['enabled'] == 'true')
-        },
-      );
+      await _vmService
+        ?.flutterToggleWidgetInspector(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -339,14 +329,10 @@
   @override
   Future<void> debugToggleProfileWidgetBuilds() async {
     try {
-      final vmservice.Response response = await _vmService
-          ?.callServiceExtension('ext.flutter.profileWidgetBuilds');
-      await _vmService?.callServiceExtension(
-        'ext.flutter.profileWidgetBuilds',
-        args: <dynamic, dynamic>{
-          'enabled': !(response.json['enabled'] == 'true')
-        },
-      );
+      await _vmService
+        ?.flutterToggleProfileWidgetBuilds(
+          isolateId: null,
+        );
     } on vmservice.RPCError {
       return;
     }
@@ -674,6 +660,14 @@
       _connectionResult = await webDevFS.connect(useDebugExtension);
       unawaited(_connectionResult.debugConnection.onDone.whenComplete(_cleanupAndExit));
 
+      _stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) {
+        final String message = utf8.decode(base64.decode(log.bytes));
+        globals.printStatus(message, newline: false);
+      });
+      _stdErrSub = _vmService.onStderrEvent.listen((vmservice.Event log) {
+        final String message = utf8.decode(base64.decode(log.bytes));
+        globals.printStatus(message, newline: false);
+      });
       try {
         await _vmService.streamListen(vmservice.EventStreams.kStdout);
       } on vmservice.RPCError {
@@ -692,14 +686,6 @@
         // It is safe to ignore this error because we expect an error to be
         // thrown if we're not already subscribed.
       }
-      _stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) {
-        final String message = utf8.decode(base64.decode(log.bytes));
-        globals.printStatus(message, newline: false);
-      });
-      _stdErrSub = _vmService.onStderrEvent.listen((vmservice.Event log) {
-        final String message = utf8.decode(base64.decode(log.bytes));
-        globals.printStatus(message, newline: false);
-      });
       unawaited(_vmService.registerService('reloadSources', 'FlutterTools'));
       _vmService.registerServiceCallback('reloadSources', (Map<String, Object> params) async {
         final bool pause = params['pause'] as bool ?? false;
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 3286a17..976e941 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -6,6 +6,7 @@
 import 'dart:math' as math;
 
 import 'package:meta/meta.dart';
+import 'package:vm_service/vm_service.dart' as vm_service;
 
 import 'android/android_device_discovery.dart';
 import 'android/android_workflow.dart';
@@ -25,7 +26,6 @@
 import 'macos/macos_device.dart';
 import 'project.dart';
 import 'tester/flutter_tester.dart';
-import 'vmservice.dart';
 import 'web/web_device.dart';
 import 'windows/windows_device.dart';
 
@@ -755,7 +755,7 @@
 
   /// Some logs can be obtained from a VM service stream.
   /// Set this after the VM services are connected.
-  VMService connectedVMService;
+  vm_service.VmService connectedVMService;
 
   @override
   String toString() => name;
@@ -785,7 +785,7 @@
   int appPid;
 
   @override
-  VMService connectedVMService;
+  vm_service.VmService connectedVMService;
 
   @override
   Stream<String> get logLines => const Stream<String>.empty();
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 4a35480..0bfd341 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -24,7 +24,6 @@
 import '../mdns_discovery.dart';
 import '../project.dart';
 import '../protocol_discovery.dart';
-import '../vmservice.dart';
 import 'fallback_discovery.dart';
 import 'ios_deploy.dart';
 import 'ios_workflow.dart';
@@ -561,18 +560,18 @@
   Stream<String> get logLines => _linesController.stream;
 
   @override
-  VMService get connectedVMService => _connectedVMService;
-  VMService _connectedVMService;
+  vm_service.VmService get connectedVMService => _connectedVMService;
+  vm_service.VmService _connectedVMService;
 
   @override
-  set connectedVMService(VMService connectedVmService) {
+  set connectedVMService(vm_service.VmService connectedVmService) {
     _listenToUnifiedLoggingEvents(connectedVmService);
     _connectedVMService = connectedVmService;
   }
 
   static const int _minimumUniversalLoggingSdkVersion = 13;
 
-  Future<void> _listenToUnifiedLoggingEvents(VMService connectedVmService) async {
+  Future<void> _listenToUnifiedLoggingEvents(vm_service.VmService connectedVmService) async {
     if (_majorSdkVersion < _minimumUniversalLoggingSdkVersion) {
       return;
     }
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 816e488..0650e27 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -152,7 +152,7 @@
   final ResidentCompiler generator;
   final BuildInfo buildInfo;
   Stream<Uri> observatoryUris;
-  VMService vmService;
+  vm_service.VmService vmService;
   DevFS devFS;
   ApplicationPackage package;
   List<String> fileSystemRoots;
@@ -226,24 +226,28 @@
     return completer.future;
   }
 
+  // TODO(jonahwilliams): remove once all callsites are updated.
+  VMService get flutterDeprecatedVmService => vmService as VMService;
+
   Future<void> refreshViews() async {
     if (vmService == null) {
       return;
     }
-    await vmService.vm.refreshViews(waitForViews: true);
+    await flutterDeprecatedVmService.vm.refreshViews(waitForViews: true);
   }
 
   List<FlutterView> get views {
-    if (vmService == null || vmService.isClosed) {
+    if (vmService == null || flutterDeprecatedVmService.isClosed) {
       return <FlutterView>[];
     }
 
+
     return (viewFilter != null
-        ? vmService.vm.allViewsWithName(viewFilter)
-        : vmService.vm.views).toList();
+        ? flutterDeprecatedVmService.vm.allViewsWithName(viewFilter)
+        : flutterDeprecatedVmService.vm.views).toList();
   }
 
-  Future<void> getVMs() => vmService.getVMOld();
+  Future<void> getVMs() => flutterDeprecatedVmService.getVMOld();
 
   Future<void> exitApps() async {
     if (!device.supportsFlutterExit) {
@@ -270,7 +274,9 @@
     for (final FlutterView view in flutterViews) {
       if (view != null && view.uiIsolate != null) {
         assert(!view.uiIsolate.pauseEvent.isPauseEvent);
-        futures.add(view.uiIsolate.flutterExit());
+        futures.add(vmService.flutterExit(
+          isolateId: view.uiIsolate.id,
+        ));
       }
     }
     // The flutterExit message only returns if it fails, so just wait a few
@@ -286,7 +292,7 @@
   }) {
     // One devFS per device. Shared by all running instances.
     devFS = DevFS(
-      vmService,
+      flutterDeprecatedVmService,
       fsName,
       rootDirectory,
       osUtils: globals.os,
@@ -301,7 +307,7 @@
     final String deviceEntryUri = devFS.baseUri
       .resolveUri(globals.fs.path.toUri(entryPath)).toString();
     return <Future<vm_service.ReloadReport>>[
-      for (final Isolate isolate in vmService.vm.isolates)
+      for (final Isolate isolate in flutterDeprecatedVmService.vm.isolates)
         vmService.reloadSources(
           isolate.id,
           pause: pause,
@@ -325,68 +331,91 @@
 
   Future<void> debugDumpApp() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterDebugDumpApp();
+      await vmService.flutterDebugDumpApp(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<void> debugDumpRenderTree() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterDebugDumpRenderTree();
+      await vmService.flutterDebugDumpRenderTree(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<void> debugDumpLayerTree() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterDebugDumpLayerTree();
+      await vmService.flutterDebugDumpLayerTree(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterDebugDumpSemanticsTreeInTraversalOrder();
+      await vmService.flutterDebugDumpSemanticsTreeInTraversalOrder(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterDebugDumpSemanticsTreeInInverseHitTestOrder();
+      await vmService.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<void> toggleDebugPaintSizeEnabled() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterToggleDebugPaintSizeEnabled();
+      await vmService.flutterToggleDebugPaintSizeEnabled(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<void> toggleDebugCheckElevationsEnabled() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterToggleDebugCheckElevationsEnabled();
+      await vmService.flutterToggleDebugCheckElevationsEnabled(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<void> debugTogglePerformanceOverlayOverride() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterTogglePerformanceOverlayOverride();
+      await vmService.flutterTogglePerformanceOverlayOverride(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<void> toggleWidgetInspector() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterToggleWidgetInspector();
+      await vmService.flutterToggleWidgetInspector(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<void> toggleProfileWidgetBuilds() async {
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterToggleProfileWidgetBuilds();
+      await vmService.flutterToggleProfileWidgetBuilds(
+        isolateId: view.uiIsolate.id,
+      );
     }
   }
 
   Future<String> togglePlatform({ String from }) async {
     final String to = nextPlatform(from, featureFlags);
     for (final FlutterView view in views) {
-      await view.uiIsolate.flutterPlatformOverride(to);
+      await vmService.flutterPlatformOverride(
+        platform: to,
+        isolateId: view.uiIsolate.id,
+      );
     }
     return to;
   }
@@ -416,7 +445,9 @@
   }
 
   Future<void> initLogReader() async {
-    (await device.getLogReader(app: package)).appPid = vmService.vm.pid;
+    final vm_service.VM vm = await vmService.getVM();
+    final DeviceLogReader logReader = await device.getLogReader(app: package);
+    logReader.appPid = vm.pid;
   }
 
   Future<int> runHot({
@@ -721,8 +752,16 @@
     String method, {
     Map<String, dynamic> params,
   }) {
-    return flutterDevices.first.views.first.uiIsolate
-        .invokeFlutterExtensionRpcRaw(method, params: params);
+    return flutterDevices
+      .first
+      .vmService
+      .invokeFlutterExtensionRpcRaw(
+        method,
+        args: params,
+        isolateId: flutterDevices
+          .first.views
+          .first.uiIsolate.id
+      );
   }
 
   /// Whether this runner can hot reload.
@@ -812,7 +851,7 @@
   void writeVmserviceFile() {
     if (debuggingOptions.vmserviceOutFile != null) {
       try {
-        final String address = flutterDevices.first.vmService.wsAddress.toString();
+        final String address = flutterDevices.first.flutterDeprecatedVmService.wsAddress.toString();
         final File vmserviceOutFile = globals.fs.file(debuggingOptions.vmserviceOutFile);
         vmserviceOutFile.createSync(recursive: true);
         vmserviceOutFile.writeAsStringSync(address);
@@ -944,7 +983,10 @@
         await device.refreshViews();
         try {
           for (final FlutterView view in device.views) {
-            await view.uiIsolate.flutterDebugAllowBanner(false);
+            await device.vmService.flutterDebugAllowBanner(
+              false,
+              isolateId: view.uiIsolate.id,
+            );
           }
         } on Exception catch (error) {
           status.cancel();
@@ -958,7 +1000,10 @@
         if (supportsServiceProtocol && isRunningDebug) {
           try {
             for (final FlutterView view in device.views) {
-              await view.uiIsolate.flutterDebugAllowBanner(true);
+              await device.vmService.flutterDebugAllowBanner(
+                true,
+                isolateId: view.uiIsolate.id,
+              );
             }
           } on Exception catch (error) {
             status.cancel();
@@ -980,7 +1025,12 @@
 
   Future<void> debugTogglePlatform() async {
     await refreshViews();
-    final String from = await flutterDevices[0].views[0].uiIsolate.flutterPlatformOverride();
+    final String isolateId = flutterDevices
+      .first.views.first.uiIsolate.id;
+    final String from = await flutterDevices
+      .first.vmService.flutterPlatformOverride(
+        isolateId: isolateId,
+      );
     String to;
     for (final FlutterDevice device in flutterDevices) {
       to = await device.togglePlatform(from: from);
@@ -1039,7 +1089,7 @@
       // This hooks up callbacks for when the connection stops in the future.
       // We don't want to wait for them. We don't handle errors in those callbacks'
       // futures either because they just print to logger and is not critical.
-      unawaited(device.vmService.done.then<void>(
+      unawaited(device.vmService.onDone.then<void>(
         _serviceProtocolDone,
         onError: _serviceProtocolError,
       ).whenComplete(_serviceDisconnected));
@@ -1056,7 +1106,7 @@
         <String, dynamic>{
           'reuseWindows': true,
         },
-        flutterDevices.first.vmService.httpAddress,
+        flutterDevices.first.flutterDeprecatedVmService.httpAddress,
         'http://${_devtoolsServer.address.host}:${_devtoolsServer.port}',
         false,  // headless mode,
         false,  // machine mode
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 1779426..1b84279 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -83,8 +83,8 @@
     if (flutterDevices.first.observatoryUris != null) {
       // For now, only support one debugger connection.
       connectionInfoCompleter?.complete(DebugConnectionInfo(
-        httpUri: flutterDevices.first.vmService.httpAddress,
-        wsUri: flutterDevices.first.vmService.wsAddress,
+        httpUri: flutterDevices.first.flutterDeprecatedVmService.httpAddress,
+        wsUri: flutterDevices.first.flutterDeprecatedVmService.wsAddress,
       ));
     }
 
@@ -105,7 +105,7 @@
       if (device.vmService != null) {
         globals.printStatus('Tracing startup on ${device.device.name}.');
         await downloadStartupTrace(
-          device.vmService,
+          device.flutterDeprecatedVmService,
           awaitFirstFrame: awaitFirstFrameWhenTracing,
         );
       }
@@ -197,7 +197,7 @@
         // Caution: This log line is parsed by device lab tests.
         globals.printStatus(
           'An Observatory debugger and profiler on $dname is available at: '
-          '${device.vmService.httpAddress}',
+          '${device.flutterDeprecatedVmService.httpAddress}',
         );
       }
     }
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 55094f4..9b11efe 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -205,7 +205,10 @@
 
     for (final FlutterDevice device in flutterDevices) {
       for (final FlutterView view in device.views) {
-        await view.uiIsolate.flutterFastReassemble(classId);
+        await device.vmService.flutterFastReassemble(
+          classId,
+          isolateId: view.uiIsolate.id,
+        );
       }
     }
 
@@ -260,8 +263,8 @@
         // Only handle one debugger connection.
         connectionInfoCompleter.complete(
           DebugConnectionInfo(
-            httpUri: flutterDevices.first.vmService.httpAddress,
-            wsUri: flutterDevices.first.vmService.wsAddress,
+            httpUri: flutterDevices.first.flutterDeprecatedVmService.httpAddress,
+            wsUri: flutterDevices.first.flutterDeprecatedVmService.wsAddress,
             baseUri: baseUris.first.toString(),
           ),
         );
@@ -570,7 +573,7 @@
       // The engine handles killing and recreating isolates that it has spawned
       // ("uiIsolates"). The isolates that were spawned from these uiIsolates
       // will not be restared, and so they must be manually killed.
-      for (final Isolate isolate in device?.vmService?.vm?.isolates ?? <Isolate>[]) {
+      for (final Isolate isolate in device?.flutterDeprecatedVmService?.vm?.isolates ?? <Isolate>[]) {
         if (!uiIsolates.contains(isolate)) {
           operations.add(isolate.invokeRpcRaw('kill', params: <String, dynamic>{
             'isolateId': isolate.id,
@@ -938,10 +941,16 @@
     }
     await Future.wait(allDevices);
 
-    // Check if any isolates are paused.
+    globals.printTrace('Evicting dirty assets');
+    await _evictDirtyAssets();
+
+    // Check if any isolates are paused and reassemble those
+    // that aren't.
     final List<FlutterView> reassembleViews = <FlutterView>[];
+    final List<Future<void>> reassembleFutures = <Future<void>>[];
     String serviceEventKind;
     int pausedIsolatesFound = 0;
+    bool failedReassemble = false;
     for (final FlutterDevice device in flutterDevices) {
       for (final FlutterView view in device.views) {
         // Check if the isolate is paused, and if so, don't reassemble. Ignore the
@@ -956,6 +965,12 @@
           }
         } else {
           reassembleViews.add(view);
+          reassembleFutures.add(device.vmService.flutterReassemble(
+            isolateId: view.uiIsolate.id,
+          ).catchError((dynamic error) {
+            failedReassemble = true;
+            globals.printError('Reassembling ${view.uiIsolate.name} failed: $error');
+          }, test: (dynamic error) => error is Exception));
         }
       }
     }
@@ -968,24 +983,10 @@
         return OperationResult(OperationResult.ok.code, reloadMessage);
       }
     }
-    globals.printTrace('Evicting dirty assets');
-    await _evictDirtyAssets();
     assert(reassembleViews.isNotEmpty);
+
     globals.printTrace('Reassembling application');
-    bool failedReassemble = false;
-    final List<Future<void>> futures = <Future<void>>[
-      for (final FlutterView view in reassembleViews)
-        () async {
-          try {
-            await view.uiIsolate.flutterReassemble();
-          } on Exception catch (error) {
-            failedReassemble = true;
-            globals.printError('Reassembling ${view.uiIsolate.name} failed: $error');
-            return;
-          }
-        }(),
-    ];
-    final Future<void> reassembleFuture = Future.wait<void>(futures);
+    final Future<void> reassembleFuture = Future.wait<void>(reassembleFutures);
     await reassembleFuture.timeout(
       const Duration(seconds: 2),
       onTimeout: () async {
@@ -1128,7 +1129,7 @@
       // Caution: This log line is parsed by device lab tests.
       globals.printStatus(
         'An Observatory debugger and profiler on $dname is available at: '
-        '${device.vmService.httpAddress}',
+        '${device.flutterDeprecatedVmService.httpAddress}',
       );
     }
   }
@@ -1144,7 +1145,13 @@
         continue;
       }
       for (final String assetPath in device.devFS.assetPathsToEvict) {
-        futures.add(device.views.first.uiIsolate.flutterEvictAsset(assetPath));
+        futures.add(
+          device.views.first.uiIsolate.vmService
+            .flutterEvictAsset(
+              assetPath,
+              isolateId: device.views.first.uiIsolate.id,
+            )
+        );
       }
       device.devFS.assetPathsToEvict.clear();
     }
diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart
index 5ea6cfb..aa41645 100644
--- a/packages/flutter_tools/lib/src/tracing.dart
+++ b/packages/flutter_tools/lib/src/tracing.dart
@@ -54,7 +54,10 @@
         });
         bool done = false;
         for (final FlutterView view in vmService.vm.views) {
-          if (await view.uiIsolate.flutterAlreadyPaintedFirstUsefulFrame()) {
+          if (await view.uiIsolate.vmService
+              .flutterAlreadyPaintedFirstUsefulFrame(
+                isolateId: view.uiIsolate.id,
+              )) {
             done = true;
             break;
           }
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 3de2238..75b6f92 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:math' as math;
 
 import 'package:meta/meta.dart' show required;
 import 'package:vm_service/vm_service.dart' as vm_service;
@@ -476,6 +475,18 @@
     return _delegateService.callMethod(method, isolateId: isolateId, args: args);
   }
 
+  @override
+  Future<void> get onDone => _delegateService.onDone;
+
+  @override
+  Future<vm_service.Response> callServiceExtension(String method,
+      {String isolateId, Map<Object, Object> args}) {
+    return _delegateService.callServiceExtension(method, isolateId: isolateId, args: args);
+  }
+
+  @override
+  Future<vm_service.VM> getVM() => _delegateService.getVM();
+
   StreamController<ServiceEvent> _getEventController(String eventName) {
     StreamController<ServiceEvent> controller = _eventControllers[eventName];
     if (controller == null) {
@@ -890,7 +901,6 @@
     _upgradeCollection(map, this);
     _loaded = true;
 
-    _pid = map['pid'] as int;
     if (map['_heapAllocatedMemoryUsage'] != null) {
       _heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage'] as int;
     }
@@ -910,10 +920,6 @@
   /// The set of live views.
   final Map<String, FlutterView> _viewCache = <String, FlutterView>{};
 
-  /// The pid of the VM's process.
-  int _pid;
-  int get pid => _pid;
-
   /// The number of bytes allocated (e.g. by malloc) in the native heap.
   int _heapAllocatedMemoryUsage;
   int get heapAllocatedMemoryUsage => _heapAllocatedMemoryUsage ?? 0;
@@ -1131,44 +1137,6 @@
   }
 }
 
-class HeapSpace extends ServiceObject {
-  HeapSpace._empty(ServiceObjectOwner owner) : super._empty(owner);
-
-  int _used = 0;
-  int _capacity = 0;
-  int _external = 0;
-  int _collections = 0;
-  double _totalCollectionTimeInSeconds = 0.0;
-  double _averageCollectionPeriodInMillis = 0.0;
-
-  int get used => _used;
-  int get capacity => _capacity;
-  int get external => _external;
-
-  Duration get avgCollectionTime {
-    final double mcs = _totalCollectionTimeInSeconds *
-      Duration.microsecondsPerSecond /
-      math.max(_collections, 1);
-    return Duration(microseconds: mcs.ceil());
-  }
-
-  Duration get avgCollectionPeriod {
-    final double mcs = _averageCollectionPeriodInMillis *
-                       Duration.microsecondsPerMillisecond;
-    return Duration(microseconds: mcs.ceil());
-  }
-
-  @override
-  void _update(Map<String, dynamic> map, bool mapIsRef) {
-    _used = map['used'] as int;
-    _capacity = map['capacity'] as int;
-    _external = map['external'] as int;
-    _collections = map['collections'] as int;
-    _totalCollectionTimeInSeconds = map['time'] as double;
-    _averageCollectionPeriodInMillis = map['avgCollectionPeriodMillis'] as double;
-  }
-}
-
 /// An isolate running inside the VM. Instances of the Isolate class are always
 /// canonicalized.
 class Isolate extends ServiceObjectOwner {
@@ -1250,118 +1218,6 @@
     pauseEvent = map['pauseEvent'] as ServiceEvent;
   }
 
-  // Flutter extension methods.
-
-  // Invoke a flutter extension method, if the flutter extension is not
-  // available, returns null.
-  Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
-    String method, {
-    Map<String, dynamic> params,
-  }) async {
-    try {
-      return await invokeRpcRaw(method, params: params);
-    } on vm_service.RPCError catch (err) {
-      // If an application is not using the framework
-      if (err.code == RPCErrorCodes.kMethodNotFound) {
-        return null;
-      }
-      rethrow;
-    }
-  }
-
-  Future<Map<String, dynamic>> flutterDebugDumpApp() {
-    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp');
-  }
-
-  Future<Map<String, dynamic>> flutterDebugDumpRenderTree() {
-    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree');
-  }
-
-  Future<Map<String, dynamic>> flutterDebugDumpLayerTree() {
-    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpLayerTree');
-  }
-
-  Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder() {
-    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInTraversalOrder');
-  }
-
-  Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder() {
-    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder');
-  }
-
-  Future<Map<String, dynamic>> _flutterToggle(String name) async {
-    Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw('ext.flutter.$name');
-    if (state != null && state.containsKey('enabled') && state['enabled'] is String) {
-      state = await invokeFlutterExtensionRpcRaw(
-        'ext.flutter.$name',
-        params: <String, dynamic>{'enabled': state['enabled'] == 'true' ? 'false' : 'true'},
-      );
-    }
-    return state;
-  }
-
-  Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled() => _flutterToggle('debugPaint');
-
-  Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled() => _flutterToggle('debugCheckElevationsEnabled');
-
-  Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride() => _flutterToggle('showPerformanceOverlay');
-
-  Future<Map<String, dynamic>> flutterToggleWidgetInspector() => _flutterToggle('inspector.show');
-
-  Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds() => _flutterToggle('profileWidgetBuilds');
-
-  Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show) {
-    return invokeFlutterExtensionRpcRaw(
-      'ext.flutter.debugAllowBanner',
-      params: <String, dynamic>{'enabled': show ? 'true' : 'false'},
-    );
-  }
-
-  Future<Map<String, dynamic>> flutterReassemble() {
-    return invokeFlutterExtensionRpcRaw('ext.flutter.reassemble');
-  }
-
-  Future<Map<String, dynamic>> flutterFastReassemble(String classId) {
-    return invokeFlutterExtensionRpcRaw('ext.flutter.fastReassemble', params: <String, Object>{
-      'class': classId,
-    });
-  }
-
-  Future<bool> flutterAlreadyPaintedFirstUsefulFrame() async {
-    final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw('ext.flutter.didSendFirstFrameRasterizedEvent');
-    // result might be null when the service extension is not initialized
-    return result != null && result['enabled'] == 'true';
-  }
-
-  Future<Map<String, dynamic>> uiWindowScheduleFrame() {
-    return invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame');
-  }
-
-  Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) {
-    return invokeFlutterExtensionRpcRaw(
-      'ext.flutter.evict',
-      params: <String, dynamic>{
-        'value': assetPath,
-      },
-    );
-  }
-
-  // Application control extension methods.
-  Future<Map<String, dynamic>> flutterExit() {
-    return invokeFlutterExtensionRpcRaw('ext.flutter.exit');
-  }
-
-  Future<String> flutterPlatformOverride([ String platform ]) async {
-    final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
-      'ext.flutter.platformOverride',
-      params: platform != null ? <String, dynamic>{'value': platform} : <String, String>{},
-    );
-    if (result != null && result['value'] is String) {
-      return result['value'] as String;
-    }
-    return 'unknown';
-  }
-
   @override
   String toString() => 'Isolate $id';
 }
@@ -1523,4 +1379,213 @@
     );
     await onRunnable;
   }
+
+  Future<Map<String, dynamic>> flutterDebugDumpApp({
+    @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.debugDumpApp',
+      isolateId: isolateId,
+    );
+  }
+
+  Future<Map<String, dynamic>> flutterDebugDumpRenderTree({
+    @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.debugDumpRenderTree',
+      isolateId: isolateId,
+    );
+  }
+
+  Future<Map<String, dynamic>> flutterDebugDumpLayerTree({
+    @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.debugDumpLayerTree',
+      isolateId: isolateId,
+    );
+  }
+
+  Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder({
+    @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
+      isolateId: isolateId,
+    );
+  }
+
+  Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder({
+    @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
+      isolateId: isolateId,
+    );
+  }
+
+  Future<Map<String, dynamic>> _flutterToggle(String name, {
+    @required String isolateId,
+  }) async {
+    Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw(
+      'ext.flutter.$name',
+      isolateId: isolateId,
+    );
+    if (state != null && state.containsKey('enabled') && state['enabled'] is String) {
+      state = await invokeFlutterExtensionRpcRaw(
+        'ext.flutter.$name',
+        isolateId: isolateId,
+        args: <String, dynamic>{
+          'enabled': state['enabled'] == 'true' ? 'false' : 'true',
+        },
+      );
+    }
+
+    return state;
+  }
+
+  Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled({
+    @required String isolateId,
+  }) => _flutterToggle('debugPaint', isolateId: isolateId);
+
+  Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled({
+    @required String isolateId,
+  }) => _flutterToggle('debugCheckElevationsEnabled', isolateId: isolateId);
+
+  Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride({
+    @required String isolateId,
+  }) => _flutterToggle('showPerformanceOverlay', isolateId: isolateId);
+
+  Future<Map<String, dynamic>> flutterToggleWidgetInspector({
+    @required String isolateId,
+  }) => _flutterToggle('inspector.show', isolateId: isolateId);
+
+  Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds({
+    @required String isolateId,
+  }) => _flutterToggle('profileWidgetBuilds', isolateId: isolateId);
+
+  Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show, {
+    @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.debugAllowBanner',
+      isolateId: isolateId,
+      args: <String, dynamic>{'enabled': show ? 'true' : 'false'},
+    );
+  }
+
+  Future<Map<String, dynamic>> flutterReassemble({
+    @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.reassemble',
+      isolateId: isolateId,
+    );
+  }
+
+  Future<Map<String, dynamic>> flutterFastReassemble(String classId, {
+   @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.fastReassemble',
+      isolateId: isolateId,
+      args: <String, Object>{
+        'class': classId,
+      },
+    );
+  }
+
+  Future<bool> flutterAlreadyPaintedFirstUsefulFrame({
+    @required String isolateId,
+  }) async {
+    final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
+      'ext.flutter.didSendFirstFrameRasterizedEvent',
+      isolateId: isolateId,
+    );
+    // result might be null when the service extension is not initialized
+    return result != null && result['enabled'] == 'true';
+  }
+
+  Future<Map<String, dynamic>> uiWindowScheduleFrame({
+    @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.ui.window.scheduleFrame',
+      isolateId: isolateId,
+    );
+  }
+
+  Future<Map<String, dynamic>> flutterEvictAsset(String assetPath, {
+   @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.evict',
+      isolateId: isolateId,
+      args: <String, dynamic>{
+        'value': assetPath,
+      },
+    );
+  }
+
+  /// Exit the application by calling [exit] from `dart:io`.
+  ///
+  /// This method is only supported by certain embedders. This is
+  /// described by [Device.supportsFlutterExit].
+  Future<Map<String, dynamic>> flutterExit({
+    @required String isolateId,
+  }) {
+    return invokeFlutterExtensionRpcRaw(
+      'ext.flutter.exit',
+      isolateId: isolateId,
+    );
+  }
+
+  /// Return the current platform override for the flutter view running with
+  /// the main isolate [isolateId].
+  ///
+  /// If a non-null value is provided for [platform], the platform override
+  /// is updated with this value.
+  Future<String> flutterPlatformOverride({
+    String platform,
+    @required String isolateId,
+  }) async {
+    final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
+      'ext.flutter.platformOverride',
+      isolateId: isolateId,
+      args: platform != null
+        ? <String, dynamic>{'value': platform}
+        : <String, String>{},
+    );
+    if (result != null && result['value'] is String) {
+      return result['value'] as String;
+    }
+    return 'unknown';
+  }
+
+  /// Invoke a flutter extension method, if the flutter extension is not
+  /// available, returns null.
+  Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
+    String method, {
+    @required String isolateId,
+    Map<String, dynamic> args,
+  }) async {
+    try {
+
+      final vm_service.Response response = await callServiceExtension(
+        method,
+        args: <String, Object>{
+          'isolateId': isolateId,
+          ...?args,
+        },
+      );
+      return response.json;
+    } on vm_service.RPCError catch (err) {
+      // If an application is not using the framework
+      if (err.code == RPCErrorCodes.kMethodNotFound) {
+        return null;
+      }
+      rethrow;
+    }
+  }
 }
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
index edef46e..5bc54f0 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -5,12 +5,17 @@
 import 'dart:async';
 
 import 'package:file/memory.dart';
+import 'package:meta/meta.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+import 'package:quiver/testing/async.dart';
+import 'package:vm_service/vm_service.dart' as vm_service;
+
 import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/base/net.dart';
-
 import 'package:flutter_tools/src/base/terminal.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/commands/attach.dart';
@@ -22,10 +27,6 @@
 import 'package:flutter_tools/src/resident_runner.dart';
 import 'package:flutter_tools/src/run_hot.dart';
 import 'package:flutter_tools/src/vmservice.dart';
-import 'package:meta/meta.dart';
-import 'package:mockito/mockito.dart';
-import 'package:process/process.dart';
-import 'package:quiver/testing/async.dart';
 import 'package:flutter_tools/src/globals.dart' as globals;
 
 import '../../src/common.dart';
@@ -142,7 +143,7 @@
           final Process dartProcess = MockProcess();
           final StreamController<List<int>> compilerStdoutController = StreamController<List<int>>();
 
-          when(dartProcess.stdout).thenAnswer((_) => compilerStdoutController.stream);
+        when(dartProcess.stdout).thenAnswer((_) => compilerStdoutController.stream);
         when(dartProcess.stderr)
           .thenAnswer((_) => Stream<List<int>>.fromFuture(Future<List<int>>.value(const <int>[])));
 
@@ -787,6 +788,23 @@
     when(vmService.done).thenAnswer((_) {
       return Future<void>.value(null);
     });
+    when(vmService.onDone).thenAnswer((_) {
+      return Future<void>.value(null);
+    });
+    when(vmService.getVM()).thenAnswer((_) async {
+      return vm_service.VM(
+        pid: 1,
+        architectureBits: 64,
+        hostCPU: '',
+        name: '',
+        isolates: <vm_service.IsolateRef>[],
+        isolateGroups: <vm_service.IsolateGroupRef>[],
+        startTime: 0,
+        targetCPU: '',
+        operatingSystem: '',
+        version: '',
+      );
+    });
 
     when(vm.refreshViews(waitForViews: anyNamed('waitForViews')))
       .thenAnswer((_) => Future<void>.value(null));
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
index 5a33104..93f1c7a 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -41,6 +41,7 @@
   ResidentRunner residentRunner;
   MockDevice mockDevice;
   MockIsolate mockIsolate;
+  FakeVmServiceHost fakeVmServiceHost;
 
   setUp(() {
     testbed = Testbed(setup: () {
@@ -89,31 +90,12 @@
         invalidatedSourcesCount: 0,
       );
     });
-    // TODO(jonahwilliams): replace mock with FakeVmServiceHost once all methods
-    // are moved to real vm service.
     when(mockFlutterDevice.devFS).thenReturn(mockDevFS);
     when(mockFlutterDevice.views).thenReturn(<FlutterView>[
       mockFlutterView,
     ]);
     when(mockFlutterDevice.device).thenReturn(mockDevice);
     when(mockFlutterView.uiIsolate).thenReturn(mockIsolate);
-    final MockVM mockVM = MockVM();
-    when(mockVMService.vm).thenReturn(mockVM);
-    when(mockVM.isolates).thenReturn(<Isolate>[mockIsolate]);
-    when(mockVMService.streamListen('Isolate')).thenAnswer((Invocation invocation) async {
-      return vm_service.Success();
-    });
-    when(mockVMService.onIsolateEvent).thenAnswer((Invocation invocation) {
-      return Stream<vm_service.Event>.fromIterable(<vm_service.Event>[
-        vm_service.Event(kind: vm_service.EventKind.kIsolateRunnable, timestamp: 0),
-      ]);
-    });
-    when(mockVMService.callMethod(
-      kRunInViewMethod,
-      args: anyNamed('args'),
-    )).thenAnswer((Invocation invocation) async {
-      return vm_service.Success();
-    });
     when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { });
     when(mockFlutterDevice.observatoryUris).thenAnswer((_) => Stream<Uri>.value(testUri));
     when(mockFlutterDevice.connect(
@@ -125,7 +107,12 @@
       .thenAnswer((Invocation invocation) async {
         return testUri;
       });
-    when(mockFlutterDevice.vmService).thenReturn(mockVMService);
+    when(mockFlutterDevice.vmService).thenAnswer((Invocation invocation) {
+      return fakeVmServiceHost.vmService;
+    });
+    when(mockFlutterDevice.flutterDeprecatedVmService).thenAnswer((Invocation invocation) {
+      return mockVMService;
+    });
     when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { });
     when(mockFlutterDevice.getVMs()).thenAnswer((Invocation invocation) async { });
     when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenReturn(<Future<vm_service.ReloadReport>>[
@@ -147,15 +134,13 @@
       final Completer<void> result = Completer<void>.sync();
       return result.future;
     });
-    when(mockIsolate.flutterExit()).thenAnswer((Invocation invocation) {
-      return Future<Map<String, Object>>.value(null);
-    });
     when(mockIsolate.reload()).thenAnswer((Invocation invocation) {
       return Future<ServiceObject>.value(null);
     });
   });
 
   test('ResidentRunner can attach to device successfully', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
     final Completer<void> onAppStart = Completer<void>.sync();
     final Future<int> result = residentRunner.attach(
@@ -174,6 +159,31 @@
   }));
 
   test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      const FakeVmServiceRequest(
+        id: '1',
+        method: 'streamListen',
+        args: <String, Object>{
+          'streamId': 'Isolate',
+        }
+      ),
+      const FakeVmServiceRequest(
+        id: '2',
+        method: kRunInViewMethod,
+        args: <String, Object>{
+          'viewId': null,
+          'mainScript': 'lib/main.dart.dill',
+          'assetDirectory': 'build/flutter_assets',
+        }
+      ),
+      FakeVmServiceStreamResponse(
+        streamId: 'Isolate',
+        event: vm_service.Event(
+          timestamp: 0,
+          kind: vm_service.EventKind.kIsolateRunnable,
+        )
+      ),
+    ]);
     when(mockDevice.supportsHotRestart).thenReturn(true);
     when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
       return 'Example';
@@ -189,7 +199,11 @@
         mockFlutterDevice,
       ],
       stayResident: false,
-      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, fastStart: true, startPaused: true),
+      debuggingOptions: DebuggingOptions.enabled(
+        BuildInfo.debug,
+        fastStart: true,
+        startPaused: true,
+      ),
     );
     final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
     final Completer<void> onAppStart = Completer<void>.sync();
@@ -209,6 +223,7 @@
   }));
 
   test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
       return 'Example';
     });
@@ -255,6 +270,16 @@
   }));
 
   test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      // Not all requests are present due to existing mocks
+      const FakeVmServiceRequest(
+        id: '1',
+        method: 'ext.flutter.reassemble',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+      ),
+    ]);
     when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
       return 'Example';
     });
@@ -284,6 +309,32 @@
   }));
 
   test('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
+     // Not all requests are present due to existing mocks
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      const FakeVmServiceRequest(
+        id: '1',
+        method: 'streamListen',
+        args: <String, Object>{
+          'streamId': 'Isolate',
+        },
+      ),
+      const FakeVmServiceRequest(
+        id: '2',
+        method: kRunInViewMethod,
+        args: <String, Object>{
+          'viewId': null,
+          'mainScript': 'lib/main.dart.dill',
+          'assetDirectory': 'build/flutter_assets',
+        },
+      ),
+      FakeVmServiceStreamResponse(
+        streamId: 'Isolate',
+        event: vm_service.Event(
+          timestamp: 0,
+          kind: vm_service.EventKind.kIsolateRunnable,
+        )
+      )
+    ]);
     when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
       return 'Example';
     });
@@ -314,6 +365,7 @@
   }));
 
   test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
       return 'Example';
     });
@@ -361,6 +413,7 @@
   }));
 
   test('ResidentRunner uses temp directory when there is no output dill path', () => testbed.run(() {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     expect(residentRunner.artifactDirectory.path, contains('flutter_tool.'));
 
     final ResidentRunner otherRunner = HotRunner(
@@ -375,6 +428,7 @@
   }));
 
   test('ResidentRunner copies output dill to cache location during preExit', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('hello');
     await residentRunner.preExit();
     final File cacheDill = globals.fs.file(globals.fs.path.join(getBuildDirectory(), 'cache.dill'));
@@ -384,6 +438,7 @@
   }));
 
   test('ResidentRunner handles output dill missing during preExit', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.preExit();
     final File cacheDill = globals.fs.file(globals.fs.path.join(getBuildDirectory(), 'cache.dill'));
 
@@ -391,6 +446,7 @@
   }));
 
   test('ResidentRunner printHelpDetails', () => testbed.run(() {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     when(mockDevice.supportsHotRestart).thenReturn(true);
     when(mockDevice.supportsScreenshot).thenReturn(true);
 
@@ -436,39 +492,48 @@
   }));
 
   test('ResidentRunner does support CanvasKit', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     expect(() => residentRunner.toggleCanvaskit(),
       throwsA(isA<Exception>()));
   }));
 
   test('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async {
-    when(mockVMService.callMethod(
-      kGetSkSLsMethod,
-      args: anyNamed('args'),
-    )).thenAnswer((Invocation invocation) async {
-      return vm_service.Response.parse(<String, Object>{
-        'SkSLs': <String, Object>{}
-      });
-    });
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+       const FakeVmServiceRequest(
+        id: '1',
+        method: kGetSkSLsMethod,
+        args: <String, Object>{
+          'viewId': null,
+        },
+        jsonResponse: <String, Object>{
+          'SkSLs': <String, Object>{}
+        }
+      )
+    ]);
     await residentRunner.writeSkSL();
 
     expect(testLogger.statusText, contains('No data was receieved'));
   }));
 
   test('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      const FakeVmServiceRequest(
+        id: '1',
+        method: kGetSkSLsMethod,
+        args: <String, Object>{
+          'viewId': null,
+        },
+        jsonResponse: <String, Object>{
+          'SkSLs': <String, Object>{
+            'A': 'B',
+          }
+        }
+      )
+    ]);
     when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
       return TargetPlatform.android_arm;
     });
     when(mockDevice.name).thenReturn('test device');
-    when(mockVMService.callMethod(
-      kGetSkSLsMethod,
-      args: anyNamed('args'),
-    )).thenAnswer((Invocation invocation) async {
-      return vm_service.Response.parse(<String, Object>{
-        'SkSLs': <String, Object>{
-          'A': 'B',
-        }
-      });
-    });
     await residentRunner.writeSkSL();
 
     expect(testLogger.statusText, contains('flutter_01.sksl'));
@@ -479,9 +544,28 @@
       'engineRevision': '42.2', // From FakeFlutterVersion
       'data': <String, Object>{'A': 'B'}
     });
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('ResidentRunner can take screenshot on debug device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      const FakeVmServiceRequest(
+        id: '1',
+        method: 'ext.flutter.debugAllowBanner',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'false',
+        },
+      ),
+      const FakeVmServiceRequest(
+        id: '2',
+        method: 'ext.flutter.debugAllowBanner',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'true',
+        },
+      )
+    ]);
     when(mockDevice.supportsScreenshot).thenReturn(true);
     when(mockDevice.takeScreenshot(any))
       .thenAnswer((Invocation invocation) async {
@@ -491,23 +575,12 @@
 
     await residentRunner.screenshot(mockFlutterDevice);
 
-    // disables debug banner.
-    verify(mockIsolate.flutterDebugAllowBanner(false)).called(1);
-    // Enables debug banner.
-    verify(mockIsolate.flutterDebugAllowBanner(true)).called(1);
     expect(testLogger.statusText, contains('1kB'));
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
-  test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws pre', () => testbed.run(() async {
-    when(mockDevice.supportsScreenshot).thenReturn(true);
-    when(mockIsolate.flutterDebugAllowBanner(false)).thenThrow(Exception());
-
-    await residentRunner.screenshot(mockFlutterDevice);
-
-    expect(testLogger.errorText, contains('Error'));
-  }));
-
-  test('ResidentTunner clears the screen when it should', () => testbed.run(() async {
+  test('ResidentRunner clears the screen when it should', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     const String message = 'This should be cleared';
     expect(testLogger.statusText, equals(''));
     testLogger.printStatus(message);
@@ -516,16 +589,72 @@
     expect(testLogger.statusText, equals(''));
   }));
 
-  test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws post', () => testbed.run(() async {
+  test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      const FakeVmServiceRequest(
+        id: '1',
+        method: 'ext.flutter.debugAllowBanner',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'false',
+        },
+        // Failed response,
+        errorCode: RPCErrorCodes.kInternalError,
+      )
+    ]);
     when(mockDevice.supportsScreenshot).thenReturn(true);
-    when(mockIsolate.flutterDebugAllowBanner(true)).thenThrow(Exception());
+    await residentRunner.screenshot(mockFlutterDevice);
 
+    expect(testLogger.errorText, contains('Error'));
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
+  }));
+
+  test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner during second request', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      const FakeVmServiceRequest(
+        id: '1',
+        method: 'ext.flutter.debugAllowBanner',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'false',
+        },
+      ),
+      const FakeVmServiceRequest(
+        id: '2',
+        method: 'ext.flutter.debugAllowBanner',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'true',
+        },
+        // Failed response,
+        errorCode: RPCErrorCodes.kInternalError,
+      )
+    ]);
+    when(mockDevice.supportsScreenshot).thenReturn(true);
     await residentRunner.screenshot(mockFlutterDevice);
 
     expect(testLogger.errorText, contains('Error'));
   }));
 
   test('ResidentRunner bails taking screenshot on debug device if takeScreenshot throws', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      const FakeVmServiceRequest(
+        id: '1',
+        method: 'ext.flutter.debugAllowBanner',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'false',
+        },
+      ),
+      const FakeVmServiceRequest(
+        id: '2',
+        method: 'ext.flutter.debugAllowBanner',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'true',
+        },
+      ),
+    ]);
     when(mockDevice.supportsScreenshot).thenReturn(true);
     when(mockDevice.takeScreenshot(any)).thenThrow(Exception());
 
@@ -535,6 +664,7 @@
   }));
 
   test("ResidentRunner can't take screenshot on device without support", () => testbed.run(() {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     when(mockDevice.supportsScreenshot).thenReturn(false);
 
     expect(() => residentRunner.screenshot(mockFlutterDevice),
@@ -542,6 +672,7 @@
   }));
 
   test('ResidentRunner does not toggle banner in non-debug mode', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     residentRunner = HotRunner(
       <FlutterDevice>[
         mockFlutterDevice,
@@ -558,18 +689,17 @@
 
     await residentRunner.screenshot(mockFlutterDevice);
 
-    // doesn't disabled debug banner.
-    verifyNever(mockIsolate.flutterDebugAllowBanner(false));
-    // doesn't enable debug banner.
-    verifyNever(mockIsolate.flutterDebugAllowBanner(true));
     expect(testLogger.statusText, contains('1kB'));
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('FlutterDevice will not exit a paused isolate', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     final TestFlutterDevice flutterDevice = TestFlutterDevice(
       mockDevice,
       <FlutterView>[ mockFlutterView ],
     );
+    flutterDevice.vmService = fakeVmServiceHost.vmService;
     final MockServiceEvent mockServiceEvent = MockServiceEvent();
     when(mockServiceEvent.isPauseEvent).thenReturn(true);
     when(mockIsolate.pauseEvent).thenReturn(mockServiceEvent);
@@ -577,15 +707,25 @@
 
     await flutterDevice.exitApps();
 
-    verifyNever(mockIsolate.flutterExit());
     verify(mockDevice.stopApp(any)).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('FlutterDevice will exit an un-paused isolate', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      const FakeVmServiceRequest(
+        id: '1',
+        method: 'ext.flutter.exit',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+      )
+    ]);
     final TestFlutterDevice flutterDevice = TestFlutterDevice(
       mockDevice,
-      <FlutterView> [mockFlutterView ],
+      <FlutterView> [mockFlutterView],
     );
+    flutterDevice.vmService = fakeVmServiceHost.vmService;
 
     final MockServiceEvent mockServiceEvent = MockServiceEvent();
     when(mockServiceEvent.isPauseEvent).thenReturn(false);
@@ -593,17 +733,18 @@
     when(mockDevice.supportsFlutterExit).thenReturn(true);
 
     await flutterDevice.exitApps();
-
-    verify(mockIsolate.flutterExit()).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('ResidentRunner refreshViews calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.refreshViews();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
   }));
 
   test('ResidentRunner debugDumpApp calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpApp();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
@@ -611,6 +752,7 @@
   }));
 
   test('ResidentRunner debugDumpRenderTree calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpRenderTree();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
@@ -618,6 +760,7 @@
   }));
 
   test('ResidentRunner debugDumpLayerTree calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpLayerTree();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
@@ -625,6 +768,7 @@
   }));
 
   test('ResidentRunner debugDumpSemanticsTreeInTraversalOrder calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpSemanticsTreeInTraversalOrder();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
@@ -632,6 +776,7 @@
   }));
 
   test('ResidentRunner debugDumpSemanticsTreeInInverseHitTestOrder calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
@@ -639,6 +784,7 @@
   }));
 
   test('ResidentRunner debugToggleDebugPaintSizeEnabled calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugToggleDebugPaintSizeEnabled();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
@@ -646,13 +792,15 @@
   }));
 
   test('ResidentRunner debugToggleDebugCheckElevationsEnabled calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugToggleDebugCheckElevationsEnabled();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.toggleDebugCheckElevationsEnabled()).called(1);
   }));
 
-  test('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(()async {
+  test('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugTogglePerformanceOverlayOverride();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
@@ -660,6 +808,7 @@
   }));
 
   test('ResidentRunner debugToggleWidgetInspector calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugToggleWidgetInspector();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
@@ -667,6 +816,7 @@
   }));
 
   test('ResidentRunner debugToggleProfileWidgetBuilds calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugToggleProfileWidgetBuilds();
 
     verify(mockFlutterDevice.refreshViews()).called(1);
@@ -674,6 +824,7 @@
   }));
 
   test('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
     residentRunner = HotRunner(
       <FlutterDevice>[
@@ -694,6 +845,7 @@
   }));
 
   test('HotRunner unforwards device ports', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     final MockDevicePortForwarder mockPortForwarder = MockDevicePortForwarder();
     when(mockDevice.portForwarder).thenReturn(mockPortForwarder);
     globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
@@ -721,6 +873,7 @@
   }));
 
   test('HotRunner handles failure to write vmservice file', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
     residentRunner = HotRunner(
       <FlutterDevice>[
@@ -744,6 +897,7 @@
 
 
   test('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
     residentRunner = ColdRunner(
       <FlutterDevice>[
@@ -764,6 +918,7 @@
   }));
 
   test('FlutterDevice uses dartdevc configuration when targeting web', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     final MockDevice mockDevice = MockDevice();
     when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
       return TargetPlatform.web_javascript;
@@ -792,6 +947,7 @@
   }));
 
   test('connect sets up log reader', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     final MockDevice mockDevice = MockDevice();
     final MockDeviceLogReader mockLogReader = MockDeviceLogReader();
     when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(mockLogReader);
diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
index b018a46..30362a7 100644
--- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
@@ -5,6 +5,8 @@
 import 'dart:async';
 import 'dart:convert';
 
+import 'package:flutter_tools/src/vmservice.dart';
+import 'package:vm_service/vm_service.dart' as vm_service;
 import 'package:dwds/dwds.dart';
 import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/io.dart';
@@ -32,11 +34,50 @@
 import '../src/context.dart';
 import '../src/testbed.dart';
 
+const List<VmServiceExpectation> kAttachLogExpectations = <VmServiceExpectation>[
+  FakeVmServiceRequest(
+    id: '1',
+    method: 'streamListen',
+    args: <String, Object>{
+      'streamId': 'Stdout',
+    },
+  ),
+  FakeVmServiceRequest(
+    id: '2',
+    method: 'streamListen',
+    args: <String, Object>{
+      'streamId': 'Stderr',
+    },
+  )
+];
+
+const List<VmServiceExpectation> kAttachIsolateExpectations = <VmServiceExpectation>[
+  FakeVmServiceRequest(
+    id: '3',
+    method: 'streamListen',
+    args: <String, Object>{
+      'streamId': 'Isolate'
+    }
+  ),
+  FakeVmServiceRequest(
+    id: '4',
+    method: 'registerService',
+    args: <String, Object>{
+      'service': 'reloadSources',
+      'alias': 'FlutterTools',
+    }
+  )
+];
+
+const List<VmServiceExpectation> kAttachExpectations = <VmServiceExpectation>[
+  ...kAttachLogExpectations,
+  ...kAttachIsolateExpectations,
+];
+
 void main() {
   Testbed testbed;
   ResidentWebRunner residentWebRunner;
   MockDebugConnection mockDebugConnection;
-  MockVmService mockVmService;
   MockChromeDevice mockChromeDevice;
   MockAppConnection mockAppConnection;
   MockFlutterDevice mockFlutterDevice;
@@ -49,11 +90,11 @@
   MockWipDebugger mockWipDebugger;
   MockWebServerDevice mockWebServerDevice;
   MockDevice mockDevice;
+  FakeVmServiceHost fakeVmServiceHost;
 
   setUp(() {
     resetChromeForTesting();
     mockDebugConnection = MockDebugConnection();
-    mockVmService = MockVmService();
     mockDevice = MockDevice();
     mockAppConnection = MockAppConnection();
     mockFlutterDevice = MockFlutterDevice();
@@ -110,24 +151,12 @@
     )).thenAnswer((Invocation _) async {
       return UpdateFSReport(success: true,  syncedBytes: 0)..invalidatedModules = <String>[];
     });
-    when(mockDebugConnection.vmService).thenReturn(mockVmService);
+    when(mockDebugConnection.vmService).thenAnswer((Invocation invocation) {
+      return fakeVmServiceHost.vmService;
+    });
     when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) {
       return Completer<void>().future;
     });
-    when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) {
-      return const Stream<Event>.empty();
-    });
-    when(mockVmService.onStderrEvent).thenAnswer((Invocation _) {
-      return const Stream<Event>.empty();
-    });
-    when(mockVmService.onDebugEvent).thenAnswer((Invocation _) {
-      return const Stream<Event>.empty();
-    });
-    when(mockVmService.onIsolateEvent).thenAnswer((Invocation _) {
-      return Stream<Event>.fromIterable(<Event>[
-        Event(kind: EventKind.kIsolateStart, timestamp: 1),
-      ]);
-    });
     when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/');
     when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS);
     when(mockWebDevFS.sources).thenReturn(<Uri>[]);
@@ -144,6 +173,7 @@
   }
 
   test('runner with web server device does not support debugging without --start-paused', () => testbed.run(() {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     when(mockFlutterDevice.device).thenReturn(WebServerDevice());
     final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
       mockFlutterDevice,
@@ -158,9 +188,11 @@
 
     when(mockFlutterDevice.device).thenReturn(MockChromeDevice());
     expect(residentWebRunner.debuggingEnabled, true);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('runner with web server device supports debugging with --start-paused', () => testbed.run(() {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     _setupMocks();
     when(mockFlutterDevice.device).thenReturn(WebServerDevice());
     final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
@@ -177,6 +209,7 @@
   }));
 
   test('profile does not supportsServiceProtocol', () => testbed.run(() {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     when(mockFlutterDevice.device).thenReturn(mockChromeDevice);
     final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner(
       mockFlutterDevice,
@@ -192,6 +225,7 @@
   }));
 
   test('Exits on run if application does not support the web', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     globals.fs.file('pubspec.yaml').createSync();
 
     expect(await residentWebRunner.run(), 1);
@@ -199,6 +233,7 @@
   }));
 
   test('Exits on run if target file does not exist', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     globals.fs.file('pubspec.yaml').createSync();
     globals.fs.file(globals.fs.path.join('web', 'index.html')).createSync(recursive: true);
 
@@ -208,6 +243,7 @@
   }));
 
   test('Can successfully run and connect to vmservice', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
     _setupMocks();
     final DelegateLogger delegateLogger = globals.logger as DelegateLogger;
     final BufferLogger bufferLogger = delegateLogger.delegate as BufferLogger;
@@ -220,7 +256,6 @@
     final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future;
 
     verify(mockAppConnection.runMain()).called(1);
-    verify(mockVmService.registerService('reloadSources', 'FlutterTools')).called(1);
     verify(status.stop()).called(1);
     verify(pub.get(
       context: PubContext.pubGet,
@@ -240,6 +275,7 @@
   }));
 
   test('Can successfully run and disconnect with --no-resident', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
     _setupMocks();
     residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
       mockFlutterDevice,
@@ -254,30 +290,28 @@
   }));
 
   test('Listens to stdout and stderr streams before running main', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachLogExpectations,
+      FakeVmServiceStreamResponse(
+        streamId: 'Stdout',
+        event: vm_service.Event(
+          timestamp: 0,
+          kind: vm_service.EventStreams.kStdout,
+          bytes: base64.encode(utf8.encode('THIS MESSAGE IS IMPORTANT'))
+        ),
+      ),
+      FakeVmServiceStreamResponse(
+        streamId: 'Stderr',
+        event: vm_service.Event(
+          timestamp: 0,
+          kind: vm_service.EventStreams.kStderr,
+          bytes: base64.encode(utf8.encode('SO IS THIS'))
+        ),
+      ),
+      ...kAttachIsolateExpectations,
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
-    final StreamController<Event> stdoutController = StreamController<Event>.broadcast();
-    final StreamController<Event> stderrController = StreamController<Event>.broadcast();
-    when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) {
-      return stdoutController.stream;
-    });
-    when(mockVmService.onStderrEvent).thenAnswer((Invocation _) {
-      return stderrController.stream;
-    });
-    when(mockAppConnection.runMain()).thenAnswer((Invocation invocation) {
-      stdoutController.add(Event.parse(<String, Object>{
-        'type': 'Event',
-        'kind': 'WriteEvent',
-        'timestamp': 1569473488296,
-        'bytes': base64.encode('THIS MESSAGE IS IMPORTANT'.codeUnits),
-      }));
-       stderrController.add(Event.parse(<String, Object>{
-        'type': 'Event',
-        'kind': 'WriteEvent',
-        'timestamp': 1569473488296,
-        'bytes': base64.encode('SO IS THIS'.codeUnits),
-      }));
-    });
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
@@ -288,6 +322,7 @@
   }));
 
   test('Does not run main with --start-paused', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
     residentWebRunner = DwdsWebRunnerFactory().createWebRunner(
       mockFlutterDevice,
       flutterProject: FlutterProject.current(),
@@ -298,10 +333,7 @@
     ) as ResidentWebRunner;
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
-    final StreamController<Event> controller = StreamController<Event>.broadcast();
-    when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) {
-      return controller.stream;
-    });
+
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
@@ -311,6 +343,17 @@
   }));
 
   test('Can hot reload after attaching', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        method: 'hotRestart',
+        id: '5',
+        args: null,
+        jsonResponse: <String, Object>{
+          'type': 'Success',
+        }
+      ),
+    ]);
     _setupMocks();
     launchChromeInstance(mockChrome);
     when(mockWebDevFS.update(
@@ -362,6 +405,17 @@
   }));
 
   test('Can hot restart after attaching', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        method: 'hotRestart',
+        id: '5',
+        args: null,
+        jsonResponse: <String, Object>{
+          'type': 'Success',
+        }
+      ),
+    ]);
     _setupMocks();
     launchChromeInstance(mockChrome);
     Uri entrypointFileUri;
@@ -417,6 +471,7 @@
   }));
 
   test('Can hot restart after attaching with web-server device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests :kAttachExpectations);
     _setupMocks();
     when(mockFlutterDevice.device).thenReturn(mockWebServerDevice);
     when(mockWebDevFS.update(
@@ -454,10 +509,13 @@
   }));
 
   test('web resident runner is debuggable', () => testbed.run(() {
+    fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
+
     expect(residentWebRunner.debuggingEnabled, true);
   }));
 
   test('web resident runner can toggle CanvasKit', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     final WebAssetServer webAssetServer = WebAssetServer(null, null, null, null, null);
     when(mockWebDevFS.webAssetServer).thenReturn(webAssetServer);
 
@@ -471,6 +529,7 @@
   }));
 
   test('Exits when initial compile fails', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     _setupMocks();
     when(mockWebDevFS.update(
       mainUri: anyNamed('mainUri'),
@@ -501,30 +560,34 @@
   }));
 
   test('Faithfully displays stdout messages with leading/trailing spaces', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachLogExpectations,
+      FakeVmServiceStreamResponse(
+        streamId: 'Stdout',
+        event: vm_service.Event(
+          timestamp: 0,
+          kind: vm_service.EventStreams.kStdout,
+          bytes: base64.encode(
+            utf8.encode('    This is a message with 4 leading and trailing spaces    '),
+          ),
+        ),
+      ),
+      ...kAttachIsolateExpectations,
+    ]);
     _setupMocks();
-    final StreamController<Event> stdoutController = StreamController<Event>();
-    when(mockVmService.onStdoutEvent).thenAnswer((Invocation invocation) {
-      return stdoutController.stream;
-    });
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
     await connectionInfoCompleter.future;
 
-    stdoutController.add(Event(
-      timestamp: 0,
-      kind: 'Stdout',
-      bytes: base64.encode(utf8.encode('    This is a message with 4 leading and trailing spaces    '))),
-    );
-    // Wait one event loop for the stream listener to fire.
-    await null;
-
     expect(testLogger.statusText,
       contains('    This is a message with 4 leading and trailing spaces    '));
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('Fails on compilation errors in hot restart', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
@@ -559,62 +622,55 @@
   }));
 
   test('Fails non-fatally on vmservice response error for hot restart', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'hotRestart',
+        args: null,
+        jsonResponse: <String, Object>{
+          'type': 'Failed',
+        }
+      )
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
     await connectionInfoCompleter.future;
-    when(mockVmService.callMethod('hotRestart')).thenAnswer((Invocation _) async {
-      return Response.parse(<String, Object>{'type': 'Failed'});
-    });
+
     final OperationResult result = await residentWebRunner.restart(fullRestart: false);
 
     expect(result.code, 0);
   }));
 
-  test('Fails fatally on vmservice RpcError', () => testbed.run(() async {
+  test('Fails fatally on Vm Service error response', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'hotRestart',
+        args: null,
+        // Failed response,
+        errorCode: RPCErrorCodes.kInternalError,
+      ),
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
     await connectionInfoCompleter.future;
-    when(mockVmService.callMethod('hotRestart')).thenThrow(RPCError('Something went wrong', 2, '123'));
     final OperationResult result = await residentWebRunner.restart(fullRestart: false);
 
     expect(result.code, 1);
-    expect(result.message, contains('Something went wrong'));
-  }));
-
-  test('Fails fatally on vmservice WipError', () => testbed.run(() async {
-    _setupMocks();
-    final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
-    unawaited(residentWebRunner.run(
-      connectionInfoCompleter: connectionInfoCompleter,
-    ));
-    await connectionInfoCompleter.future;
-    when(mockVmService.callMethod('hotRestart')).thenThrow(WipError(<String, String>{}));
-    final OperationResult result = await residentWebRunner.restart(fullRestart: false);
-
-    expect(result.code, 1);
-  }));
-
-  test('Fails fatally on vmservice Exception', () => testbed.run(() async {
-    _setupMocks();
-    final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
-    unawaited(residentWebRunner.run(
-      connectionInfoCompleter: connectionInfoCompleter,
-    ));
-    await connectionInfoCompleter.future;
-    when(mockVmService.callMethod('hotRestart')).thenThrow(Exception('Something went wrong'));
-    final OperationResult result = await residentWebRunner.restart(fullRestart: false);
-
-    expect(result.code, 1);
-    expect(result.message, contains('Something went wrong'));
+    expect(result.message,
+      contains(RPCErrorCodes.kInternalError.toString()));
   }));
 
   test('printHelp without details has web warning', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     residentWebRunner.printHelp(details: false);
 
     expect(testLogger.statusText, contains('Warning'));
@@ -623,6 +679,16 @@
   }));
 
   test('debugDumpApp', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.debugDumpApp',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+      ),
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
@@ -631,10 +697,20 @@
     await connectionInfoCompleter.future;
     await residentWebRunner.debugDumpApp();
 
-    verify(mockVmService.callServiceExtension('ext.flutter.debugDumpApp')).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('debugDumpLayerTree', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.debugDumpLayerTree',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+      ),
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
@@ -643,10 +719,20 @@
     await connectionInfoCompleter.future;
     await residentWebRunner.debugDumpLayerTree();
 
-    verify(mockVmService.callServiceExtension('ext.flutter.debugDumpLayerTree')).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('debugDumpRenderTree', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.debugDumpRenderTree',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+      ),
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
@@ -655,10 +741,20 @@
     await connectionInfoCompleter.future;
     await residentWebRunner.debugDumpRenderTree();
 
-    verify(mockVmService.callServiceExtension('ext.flutter.debugDumpRenderTree')).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('debugDumpSemanticsTreeInTraversalOrder', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+      ),
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
@@ -667,113 +763,224 @@
     await connectionInfoCompleter.future;
     await residentWebRunner.debugDumpSemanticsTreeInTraversalOrder();
 
-    verify(mockVmService.callServiceExtension('ext.flutter.debugDumpSemanticsTreeInTraversalOrder')).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('debugDumpSemanticsTreeInInverseHitTestOrder', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+      ),
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
+
     await connectionInfoCompleter.future;
     await residentWebRunner.debugDumpSemanticsTreeInInverseHitTestOrder();
 
-    verify(mockVmService.callServiceExtension('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder')).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('debugToggleDebugPaintSizeEnabled', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.debugPaint',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+        jsonResponse: <String, Object>{
+          'enabled': 'false'
+        },
+      ),
+      const FakeVmServiceRequest(
+        id: '6',
+        method: 'ext.flutter.debugPaint',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'true',
+        },
+        jsonResponse: <String, Object>{
+          'value': 'true'
+        },
+      )
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
     await connectionInfoCompleter.future;
-    when(mockVmService.callServiceExtension('ext.flutter.debugPaint'))
-      .thenAnswer((Invocation _) async {
-        return Response.parse(<String, Object>{'enabled': false});
-    });
+
     await residentWebRunner.debugToggleDebugPaintSizeEnabled();
 
-    verify(mockVmService.callServiceExtension('ext.flutter.debugPaint',
-        args: <String, Object>{'enabled': true})).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
 
   test('debugTogglePerformanceOverlayOverride', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.showPerformanceOverlay',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+        jsonResponse: <String, Object>{
+          'enabled': 'false'
+        },
+      ),
+      const FakeVmServiceRequest(
+        id: '6',
+        method: 'ext.flutter.showPerformanceOverlay',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'true',
+        },
+        jsonResponse: <String, Object>{
+          'enabled': 'true'
+        },
+      )
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
     await connectionInfoCompleter.future;
-    when(mockVmService.callServiceExtension('ext.flutter.showPerformanceOverlay'))
-      .thenAnswer((Invocation _) async {
-        return Response.parse(<String, Object>{'enabled': false});
-    });
 
     await residentWebRunner.debugTogglePerformanceOverlayOverride();
 
-    verify(mockVmService.callServiceExtension('ext.flutter.showPerformanceOverlay',
-        args: <String, Object>{'enabled': true})).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('debugToggleWidgetInspector', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.inspector.show',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+        jsonResponse: <String, Object>{
+          'enabled': 'false'
+        },
+      ),
+      const FakeVmServiceRequest(
+        id: '6',
+        method: 'ext.flutter.inspector.show',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'true',
+        },
+        jsonResponse: <String, Object>{
+          'enabled': 'true'
+        },
+      )
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
     await connectionInfoCompleter.future;
-    when(mockVmService.callServiceExtension('ext.flutter.debugToggleWidgetInspector'))
-      .thenAnswer((Invocation _) async {
-        return Response.parse(<String, Object>{'enabled': false});
-    });
 
     await residentWebRunner.debugToggleWidgetInspector();
 
-    verify(mockVmService.callServiceExtension('ext.flutter.debugToggleWidgetInspector',
-        args: <String, Object>{'enabled': true})).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('debugToggleProfileWidgetBuilds', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.profileWidgetBuilds',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+        jsonResponse: <String, Object>{
+          'enabled': 'false'
+        },
+      ),
+      const FakeVmServiceRequest(
+        id: '6',
+        method: 'ext.flutter.profileWidgetBuilds',
+        args: <String, Object>{
+          'isolateId': null,
+          'enabled': 'true',
+        },
+        jsonResponse: <String, Object>{
+          'enabled': 'true'
+        },
+      )
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
     await connectionInfoCompleter.future;
-    when(mockVmService.callServiceExtension('ext.flutter.profileWidgetBuilds'))
-      .thenAnswer((Invocation _) async {
-        return Response.parse(<String, Object>{'enabled': false});
-    });
 
     await residentWebRunner.debugToggleProfileWidgetBuilds();
 
-    verify(mockVmService.callServiceExtension('ext.flutter.profileWidgetBuilds',
-        args: <String, Object>{'enabled': true})).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('debugTogglePlatform', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+      const FakeVmServiceRequest(
+        id: '5',
+        method: 'ext.flutter.platformOverride',
+        args: <String, Object>{
+          'isolateId': null,
+        },
+        jsonResponse: <String, Object>{
+          'value': 'iOS'
+        },
+      ),
+      const FakeVmServiceRequest(
+        id: '6',
+        method: 'ext.flutter.platformOverride',
+        args: <String, Object>{
+          'isolateId': null,
+          'value': 'fuchsia',
+        },
+        jsonResponse: <String, Object>{
+          'value': 'fuchsia'
+        },
+      ),
+    ]);
     _setupMocks();
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
     unawaited(residentWebRunner.run(
       connectionInfoCompleter: connectionInfoCompleter,
     ));
     await connectionInfoCompleter.future;
-    when(mockVmService.callServiceExtension('ext.flutter.platformOverride'))
-      .thenAnswer((Invocation _) async {
-        return Response.parse(<String, Object>{'value': 'iOS'});
-    });
 
     await residentWebRunner.debugTogglePlatform();
 
-    expect(testLogger.statusText, contains('Switched operating system to fuchsia'));
-    verify(mockVmService.callServiceExtension('ext.flutter.platformOverride',
-        args: <String, Object>{'value': 'fuchsia'})).called(1);
+    expect(testLogger.statusText,
+      contains('Switched operating system to fuchsia'));
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('cleanup of resources is safe to call multiple times', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+    ]);
     _setupMocks();
     bool debugClosed = false;
     when(mockDevice.stopApp(any)).thenAnswer((Invocation invocation) async {
@@ -793,9 +1000,13 @@
     await residentWebRunner.exit();
 
     verifyNever(mockDebugConnection.close());
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('cleans up Chrome if tab is closed', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+    ]);
     _setupMocks();
     final Completer<void> onDone = Completer<void>();
     when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) {
@@ -809,9 +1020,13 @@
     onDone.complete();
 
     await result;
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('Prints target and device name on run', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachExpectations,
+    ]);
     _setupMocks();
     when(mockDevice.name).thenReturn('Chromez');
     final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
@@ -820,10 +1035,32 @@
     ));
     await connectionInfoCompleter.future;
 
-    expect(testLogger.statusText, contains('Launching ${globals.fs.path.join('lib', 'main.dart')} on Chromez in debug mode'));
+    expect(testLogger.statusText, contains(
+      'Launching ${globals.fs.path.join('lib', 'main.dart')} on '
+      'Chromez in debug mode',
+    ));
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('Sends launched app.webLaunchUrl event for Chrome device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+      ...kAttachLogExpectations,
+      const FakeVmServiceRequest(
+        id: '3',
+        method: 'streamListen',
+        args: <String, Object>{
+          'streamId': 'Isolate'
+        }
+      ),
+      const FakeVmServiceRequest(
+        id: '4',
+        method: 'registerService',
+        args: <String, Object>{
+          'service': 'reloadSources',
+          'alias': 'FlutterTools',
+        }
+      )
+    ]);
     _setupMocks();
     when(mockFlutterDevice.device).thenReturn(ChromeDevice());
     when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async {
@@ -870,12 +1107,14 @@
         },
       },
     )));
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }, overrides: <Type, Generator>{
     Logger: () => DelegateLogger(BufferLogger.test()),
     ChromeLauncher: () => MockChromeLauncher(),
   }));
 
   test('Sends unlaunched app.webLaunchUrl event for Web Server device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     _setupMocks();
     when(mockFlutterDevice.device).thenReturn(WebServerDevice());
     when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async {
@@ -910,106 +1149,65 @@
         },
       },
     )));
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }, overrides: <Type, Generator>{
     Logger: () => DelegateLogger(BufferLogger.test())
   }));
 
   test('Successfully turns WebSocketException into ToolExit', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     _setupMocks();
-    final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
-    final Completer<void> unhandledErrorCompleter = Completer<void>();
-     when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async {
-      unawaited(unhandledErrorCompleter.future.then((void value) {
-        throw const WebSocketException();
-      }));
-      return ConnectionResult(mockAppConnection, mockDebugConnection);
-    });
 
-    final Future<void> expectation = expectLater(() => residentWebRunner.run(
-      connectionInfoCompleter: connectionInfoCompleter,
-    ), throwsToolExit());
+    when(mockWebDevFS.connect(any))
+      .thenThrow(const WebSocketException());
 
-    unhandledErrorCompleter.complete();
-    await expectation;
+    await expectLater(() => residentWebRunner.run(), throwsToolExit());
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('Successfully turns AppConnectionException into ToolExit', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     _setupMocks();
-    final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
-    final Completer<void> unhandledErrorCompleter = Completer<void>();
-    when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async {
-      unawaited(unhandledErrorCompleter.future.then((void value) {
-        throw AppConnectionException('Could not connect to application with appInstanceId: c0ae0750-ee91-11e9-cea6-35d95a968356');
-      }));
-      return ConnectionResult(mockAppConnection, mockDebugConnection);
-    });
 
-    final Future<void> expectation = expectLater(() => residentWebRunner.run(
-      connectionInfoCompleter: connectionInfoCompleter,
-    ), throwsToolExit());
+    when(mockWebDevFS.connect(any))
+      .thenThrow(AppConnectionException(''));
 
-    unhandledErrorCompleter.complete();
-    await expectation;
+    await expectLater(() => residentWebRunner.run(), throwsToolExit());
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('Successfully turns ChromeDebugError into ToolExit', () => testbed.run(() async {
-     _setupMocks();
-    final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
-    final Completer<void> unhandledErrorCompleter = Completer<void>();
-     when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async {
-      unawaited(unhandledErrorCompleter.future.then((void value) {
-        throw ChromeDebugException(<String, dynamic>{});
-      }));
-      return ConnectionResult(mockAppConnection, mockDebugConnection);
-    });
-
-    final Future<void> expectation = expectLater(() => residentWebRunner.run(
-      connectionInfoCompleter: connectionInfoCompleter,
-    ), throwsToolExit());
-
-    unhandledErrorCompleter.complete();
-    await expectation;
-  }));
-
-  test('Rethrows Exception type', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     _setupMocks();
-    final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
-    final Completer<void> unhandledErrorCompleter = Completer<void>();
-     when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async {
-      unawaited(unhandledErrorCompleter.future.then((void value) {
-        throw Exception('Something went wrong');
-      }));
-      return ConnectionResult(mockAppConnection, mockDebugConnection);
-    });
 
-    final Future<void> expectation = expectLater(() => residentWebRunner.run(
-      connectionInfoCompleter: connectionInfoCompleter,
-    ), throwsException);
+    when(mockWebDevFS.connect(any))
+      .thenThrow(ChromeDebugException(<String, dynamic>{}));
 
-    unhandledErrorCompleter.complete();
-    await expectation;
+    await expectLater(() => residentWebRunner.run(), throwsToolExit());
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
-  test('Rethrows unknown exception type from web tooling', () => testbed.run(() async {
+
+  test('Rethrows unknown Exception type from dwds', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
+    _setupMocks();
+    when(mockWebDevFS.connect(any)).thenThrow(Exception());
+
+    await expectLater(() => residentWebRunner.run(), throwsException);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
+  }));
+
+  test('Rethrows unknown Error type from dwds tooling', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     _setupMocks();
     final DelegateLogger delegateLogger = globals.logger as DelegateLogger;
     final MockStatus mockStatus = MockStatus();
     delegateLogger.status = mockStatus;
-    final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
-    final Completer<void> unhandledErrorCompleter = Completer<void>();
-    when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async {
-      unawaited(unhandledErrorCompleter.future.then((void value) {
-        throw StateError('Something went wrong');
-      }));
-      return ConnectionResult(mockAppConnection, mockDebugConnection);
-    });
 
-    final Future<void> expectation = expectLater(() => residentWebRunner.run(
-      connectionInfoCompleter: connectionInfoCompleter,
-    ), throwsStateError);
+    when(mockWebDevFS.connect(any)).thenThrow(StateError(''));
 
-    unhandledErrorCompleter.complete();
-    await expectation;
+    await expectLater(() => residentWebRunner.run(), throwsStateError);
     verify(mockStatus.stop()).called(1);
+    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }, overrides: <Type, Generator>{
     Logger: () => DelegateLogger(BufferLogger(
       terminal: AnsiTerminal(
diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart
index 9439e85..88db3c1 100644
--- a/packages/flutter_tools/test/general.shard/vmservice_test.dart
+++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart
@@ -6,7 +6,6 @@
 
 import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/convert.dart';
-import 'package:meta/meta.dart';
 import 'package:vm_service/vm_service.dart' as vm_service;
 import 'package:mockito/mockito.dart';
 import 'package:flutter_tools/src/base/logger.dart';
@@ -285,10 +284,10 @@
   testWithoutContext('runInView forwards arguments correctly', () async {
     final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
       requests: <VmServiceExpectation>[
-        const FakeVmServiceRequest(method: 'streamListen', id: '1', params: <String, Object>{
+        const FakeVmServiceRequest(method: 'streamListen', id: '1', args: <String, Object>{
           'streamId': 'Isolate'
         }),
-        const FakeVmServiceRequest(method: kRunInViewMethod, id: '2', params: <String, Object>{
+        const FakeVmServiceRequest(method: kRunInViewMethod, id: '2', args: <String, Object>{
           'viewId': '1234',
           'mainScript': 'main.dart',
           'assetDirectory': 'flutter_assets/',
@@ -312,95 +311,6 @@
   });
 }
 
-class FakeVmServiceHost {
-  FakeVmServiceHost({
-    @required List<VmServiceExpectation> requests,
-  }) : _requests = requests {
-    _vmService = vm_service.VmService(
-      _input.stream,
-      _output.add,
-    );
-    _applyStreamListen();
-    _output.stream.listen((String data) {
-      final Map<String, Object> request = json.decode(data) as Map<String, Object>;
-      if (_requests.isEmpty) {
-        throw Exception('Unexpected request: $request');
-      }
-      final FakeVmServiceRequest fakeRequest = _requests.removeAt(0) as FakeVmServiceRequest;
-      expect(fakeRequest, isA<FakeVmServiceRequest>()
-        .having((FakeVmServiceRequest request) => request.method, 'method', request['method'])
-        .having((FakeVmServiceRequest request) => request.id, 'id', request['id'])
-        .having((FakeVmServiceRequest request) => request.params, 'params', request['params'])
-      );
-      _input.add(json.encode(<String, Object>{
-        'jsonrpc': '2.0',
-        'id': fakeRequest.id,
-        'result': fakeRequest.jsonResponse ?? <String, Object>{'type': 'Success'},
-      }));
-      _applyStreamListen();
-    });
-  }
-
-  final List<VmServiceExpectation> _requests;
-  final StreamController<String> _input = StreamController<String>();
-  final StreamController<String> _output = StreamController<String>();
-
-  vm_service.VmService get vmService => _vmService;
-  vm_service.VmService _vmService;
-
-  bool get hasRemainingExpectations => _requests.isNotEmpty;
-
-  // remove FakeStreamResponse objects from _requests until it is empty
-  // or until we hit a FakeRequest
-  void _applyStreamListen() {
-    while (_requests.isNotEmpty && !_requests.first.isRequest) {
-      final FakeVmServiceStreamResponse response = _requests.removeAt(0) as FakeVmServiceStreamResponse;
-      _input.add(json.encode(<String, Object>{
-        'jsonrpc': '2.0',
-        'method': 'streamNotify',
-        'params': <String, Object>{
-          'streamId': response.streamId,
-          'event': response.event.toJson(),
-        },
-      }));
-    }
-  }
-}
-
-abstract class VmServiceExpectation {
-  bool get isRequest;
-}
-
-class FakeVmServiceRequest implements VmServiceExpectation {
-  const FakeVmServiceRequest({
-    @required this.method,
-    @required this.id,
-    @required this.params,
-    this.jsonResponse,
-  });
-
-  final String method;
-  final String id;
-  final Map<String, Object> params;
-  final Map<String, Object> jsonResponse;
-
-  @override
-  bool get isRequest => true;
-}
-
-class FakeVmServiceStreamResponse implements VmServiceExpectation {
-  const FakeVmServiceStreamResponse({
-    @required this.event,
-    @required this.streamId,
-  });
-
-  final vm_service.Event event;
-  final String streamId;
-
-  @override
-  bool get isRequest => false;
-}
-
 class MockDevice extends Mock implements Device {}
 class MockVMService extends Mock implements vm_service.VmService {}
 class MockFlutterVersion extends Mock implements FlutterVersion {
diff --git a/packages/flutter_tools/test/src/common.dart b/packages/flutter_tools/test/src/common.dart
index ecf413d..298b5e7 100644
--- a/packages/flutter_tools/test/src/common.dart
+++ b/packages/flutter_tools/test/src/common.dart
@@ -5,10 +5,12 @@
 import 'dart:async';
 
 import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/convert.dart';
+import 'package:vm_service/vm_service.dart' as vm_service;
+
 import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
-
 import 'package:flutter_tools/src/base/process.dart';
 import 'package:flutter_tools/src/commands/create.dart';
 import 'package:flutter_tools/src/runner/flutter_command.dart';
@@ -217,3 +219,109 @@
     return body();
   }
 }
+
+/// A fake implementation of a vm_service that mocks the JSON-RPC request
+/// and response structure.
+class FakeVmServiceHost {
+  FakeVmServiceHost({
+    @required List<VmServiceExpectation> requests,
+  }) : _requests = requests {
+    _vmService = vm_service.VmService(
+      _input.stream,
+      _output.add,
+    );
+    _applyStreamListen();
+    _output.stream.listen((String data) {
+      final Map<String, Object> request = json.decode(data) as Map<String, Object>;
+      if (_requests.isEmpty) {
+        throw Exception('Unexpected request: $request');
+      }
+      final FakeVmServiceRequest fakeRequest = _requests.removeAt(0) as FakeVmServiceRequest;
+      expect(request, isA<Map<String, Object>>()
+        .having((Map<String, Object> request) => request['method'], 'method', fakeRequest.method)
+        .having((Map<String, Object> request) => request['id'], 'id', fakeRequest.id)
+        .having((Map<String, Object> request) => request['params'], 'args', fakeRequest.args)
+      );
+      if (fakeRequest.errorCode == null) {
+        _input.add(json.encode(<String, Object>{
+          'jsonrpc': '2.0',
+          'id': fakeRequest.id,
+          'result': fakeRequest.jsonResponse ?? <String, Object>{'type': 'Success'},
+        }));
+      } else {
+        _input.add(json.encode(<String, Object>{
+          'jsonrpc': '2.0',
+          'id': fakeRequest.id,
+          'error': <String, Object>{
+            'code': fakeRequest.errorCode,
+          }
+        }));
+      }
+      _applyStreamListen();
+    });
+  }
+
+  final List<VmServiceExpectation> _requests;
+  final StreamController<String> _input = StreamController<String>();
+  final StreamController<String> _output = StreamController<String>();
+
+  vm_service.VmService get vmService => _vmService;
+  vm_service.VmService _vmService;
+
+  bool get hasRemainingExpectations => _requests.isNotEmpty;
+
+  // remove FakeStreamResponse objects from _requests until it is empty
+  // or until we hit a FakeRequest
+  void _applyStreamListen() {
+    while (_requests.isNotEmpty && !_requests.first.isRequest) {
+      final FakeVmServiceStreamResponse response = _requests.removeAt(0) as FakeVmServiceStreamResponse;
+      _input.add(json.encode(<String, Object>{
+        'jsonrpc': '2.0',
+        'method': 'streamNotify',
+        'params': <String, Object>{
+          'streamId': response.streamId,
+          'event': response.event.toJson(),
+        },
+      }));
+    }
+  }
+}
+
+abstract class VmServiceExpectation {
+  bool get isRequest;
+}
+
+class FakeVmServiceRequest implements VmServiceExpectation {
+  const FakeVmServiceRequest({
+    @required this.method,
+    @required this.id,
+    @required this.args,
+    this.jsonResponse,
+    this.errorCode,
+  });
+
+  final String method;
+  final String id;
+
+  /// If non-null, the error code for a [vm_service.RPCError] in place of a
+  /// standard response.
+  final int errorCode;
+  final Map<String, Object> args;
+  final Map<String, Object> jsonResponse;
+
+  @override
+  bool get isRequest => true;
+}
+
+class FakeVmServiceStreamResponse implements VmServiceExpectation {
+  const FakeVmServiceStreamResponse({
+    @required this.event,
+    @required this.streamId,
+  });
+
+  final vm_service.Event event;
+  final String streamId;
+
+  @override
+  bool get isRequest => false;
+}