Move plugin injection to just after pub get (#14743)

diff --git a/packages/flutter_tools/test/commands/packages_test.dart b/packages/flutter_tools/test/commands/packages_test.dart
index 86bbc28..34f6fb8 100644
--- a/packages/flutter_tools/test/commands/packages_test.dart
+++ b/packages/flutter_tools/test/commands/packages_test.dart
@@ -29,9 +29,19 @@
       temp.deleteSync(recursive: true);
     });
 
-    Future<String> runCommand(String verb, { List<String> args }) async {
+    Future<String> createProjectWithPlugin(String plugin) async {
       final String projectPath = await createProject(temp);
+      final File pubspec = fs.file(fs.path.join(projectPath, 'pubspec.yaml'));
+      String content = await pubspec.readAsString();
+      content = content.replaceFirst(
+        '\ndependencies:\n',
+        '\ndependencies:\n  $plugin:\n',
+      );
+      await pubspec.writeAsString(content, flush: true);
+      return projectPath;
+    }
 
+    Future<Null> runCommandIn(String projectPath, String verb, { List<String> args }) async {
       final PackagesCommand command = new PackagesCommand();
       final CommandRunner<Null> runner = createTestCommandRunner(command);
 
@@ -41,31 +51,148 @@
       commandArgs.add(projectPath);
 
       await runner.run(commandArgs);
-
-      return projectPath;
     }
 
     void expectExists(String projectPath, String relPath) {
-      expect(fs.isFileSync(fs.path.join(projectPath, relPath)), true);
+      expect(
+        fs.isFileSync(fs.path.join(projectPath, relPath)),
+        true,
+        reason: '$projectPath/$relPath should exist, but does not',
+      );
     }
 
-    // Verify that we create a project that is well-formed.
-    testUsingContext('get', () async {
-      final String projectPath = await runCommand('get');
-      expectExists(projectPath, 'lib/main.dart');
-      expectExists(projectPath, '.packages');
+    void expectContains(String projectPath, String relPath, String substring) {
+      expectExists(projectPath, relPath);
+      expect(
+        fs.file(fs.path.join(projectPath, relPath)).readAsStringSync(),
+        contains(substring),
+        reason: '$projectPath/$relPath has unexpected content'
+      );
+    }
+
+    void expectNotExists(String projectPath, String relPath) {
+      expect(
+        fs.isFileSync(fs.path.join(projectPath, relPath)),
+        false,
+        reason: '$projectPath/$relPath should not exist, but does',
+      );
+    }
+
+    void expectNotContains(String projectPath, String relPath, String substring) {
+      expectExists(projectPath, relPath);
+      expect(
+        fs.file(fs.path.join(projectPath, relPath)).readAsStringSync(),
+        isNot(contains(substring)),
+        reason: '$projectPath/$relPath has unexpected content',
+      );
+    }
+
+    const List<String> pubOutput = const <String>[
+      '.packages',
+      'pubspec.lock',
+    ];
+
+    const List<String> pluginRegistrants = const <String>[
+      'ios/Runner/GeneratedPluginRegistrant.h',
+      'ios/Runner/GeneratedPluginRegistrant.m',
+      'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
+    ];
+
+    const List<String> pluginWitnesses = const <String>[
+      '.flutter-plugins',
+      'ios/Podfile',
+    ];
+
+    const Map<String, String> pluginContentWitnesses = const <String, String>{
+      'ios/Flutter/Debug.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
+      'ios/Flutter/Release.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
+    };
+
+    void expectDependenciesResolved(String projectPath) {
+      for (String output in pubOutput) {
+        expectExists(projectPath, output);
+      }
+    }
+
+    void expectZeroPluginsInjected(String projectPath) {
+      for (final String registrant in pluginRegistrants) {
+        expectExists(projectPath, registrant);
+      }
+      for (final String witness in pluginWitnesses) {
+        expectNotExists(projectPath, witness);
+      }
+      pluginContentWitnesses.forEach((String witness, String content) {
+        expectNotContains(projectPath, witness, content);
+      });
+    }
+
+    void expectPluginInjected(String projectPath) {
+      for (final String registrant in pluginRegistrants) {
+        expectExists(projectPath, registrant);
+      }
+      for (final String witness in pluginWitnesses) {
+        expectExists(projectPath, witness);
+      }
+      pluginContentWitnesses.forEach((String witness, String content) {
+        expectContains(projectPath, witness, content);
+      });
+    }
+
+    void removeGeneratedFiles(String projectPath) {
+      final Iterable<String> allFiles = <List<String>>[
+        pubOutput,
+        pluginRegistrants,
+        pluginWitnesses,
+      ].expand((List<String> list) => list);
+      for (String path in allFiles) {
+        final File file = fs.file(fs.path.join(projectPath, path));
+        if (file.existsSync())
+          file.deleteSync();
+      }
+    }
+
+    testUsingContext('get fetches packages', () async {
+      final String projectPath = await createProject(temp);
+
+      removeGeneratedFiles(projectPath);
+
+      await runCommandIn(projectPath, 'get');
+
+      expectDependenciesResolved(projectPath);
+      expectZeroPluginsInjected(projectPath);
     }, timeout: allowForRemotePubInvocation);
 
-    testUsingContext('get --offline', () async {
-      final String projectPath = await runCommand('get', args: <String>['--offline']);
-      expectExists(projectPath, 'lib/main.dart');
-      expectExists(projectPath, '.packages');
-    });
+    testUsingContext('get --offline fetches packages', () async {
+      final String projectPath = await createProject(temp);
 
-    testUsingContext('upgrade', () async {
-      final String projectPath = await runCommand('upgrade');
-      expectExists(projectPath, 'lib/main.dart');
-      expectExists(projectPath, '.packages');
+      removeGeneratedFiles(projectPath);
+
+      await runCommandIn(projectPath, 'get', args: <String>['--offline']);
+
+      expectDependenciesResolved(projectPath);
+      expectZeroPluginsInjected(projectPath);
+    }, timeout: allowForCreateFlutterProject);
+
+    testUsingContext('upgrade fetches packages', () async {
+      final String projectPath = await createProject(temp);
+
+      removeGeneratedFiles(projectPath);
+
+      await runCommandIn(projectPath, 'upgrade');
+
+      expectDependenciesResolved(projectPath);
+      expectZeroPluginsInjected(projectPath);
+    }, timeout: allowForRemotePubInvocation);
+
+    testUsingContext('get fetches packages and injects plugin', () async {
+      final String projectPath = await createProjectWithPlugin('path_provider');
+
+      removeGeneratedFiles(projectPath);
+
+      await runCommandIn(projectPath, 'get');
+
+      expectDependenciesResolved(projectPath);
+      expectPluginInjected(projectPath);
     }, timeout: allowForRemotePubInvocation);
   });
 
diff --git a/packages/flutter_tools/test/ios/cocoapods_test.dart b/packages/flutter_tools/test/ios/cocoapods_test.dart
index 531c8ab..77c1e6d 100644
--- a/packages/flutter_tools/test/ios/cocoapods_test.dart
+++ b/packages/flutter_tools/test/ios/cocoapods_test.dart
@@ -6,9 +6,11 @@
 
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/io.dart';
-import 'package:flutter_tools/src/ios/cocoapods.dart';
 import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/ios/cocoapods.dart';
+import 'package:flutter_tools/src/ios/xcodeproj.dart';
 import 'package:mockito/mockito.dart';
 import 'package:process/process.dart';
 import 'package:test/test.dart';
@@ -18,6 +20,7 @@
 void main() {
   FileSystem fs;
   ProcessManager mockProcessManager;
+  MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
   Directory projectUnderTest;
   CocoaPods cocoaPodsUnderTest;
 
@@ -25,7 +28,9 @@
     Cache.flutterRoot = 'flutter';
     fs = new MemoryFileSystem();
     mockProcessManager = new MockProcessManager();
+    mockXcodeProjectInterpreter = new MockXcodeProjectInterpreter();
     projectUnderTest = fs.directory(fs.path.join('project', 'ios'))..createSync(recursive: true);
+
     fs.file(fs.path.join(
       Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-objc'
     ))
@@ -45,97 +50,122 @@
     )).thenReturn(exitsHappy);
   });
 
-  testUsingContext(
-    'create objective-c Podfile when not present',
-    () async {
-      await cocoaPodsUnderTest.processPods(
-        appIosDir: projectUnderTest,
-        iosEngineDir: 'engine/path',
-      );
-      expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Objective-C podfile template');
-      verify(mockProcessManager.run(
-        <String>['pod', 'install', '--verbose'],
-        workingDirectory: 'project/ios',
-        environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
-      ));
-    },
-    overrides: <Type, Generator>{
-      FileSystem: () => fs,
-      ProcessManager: () => mockProcessManager,
-    },
-  );
+  group('Setup Podfile', () {
+    File podfile;
+    File debugConfigFile;
+    File releaseConfigFile;
 
-  testUsingContext(
-    'create swift Podfile if swift',
-    () async {
-      await cocoaPodsUnderTest.processPods(
-        appIosDir: projectUnderTest,
-        iosEngineDir: 'engine/path',
-        isSwift: true,
-      );
-      expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Swift podfile template');
-      verify(mockProcessManager.run(
-        <String>['pod', 'install', '--verbose'],
-        workingDirectory: 'project/ios',
-        environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
-      ));
-    },
-    overrides: <Type, Generator>{
-      FileSystem: () => fs,
-      ProcessManager: () => mockProcessManager,
-    },
-  );
+    setUp(() {
+      debugConfigFile = fs.file(fs.path.join('project', 'ios', 'Flutter', 'Debug.xcconfig'));
+      releaseConfigFile = fs.file(fs.path.join('project', 'ios', 'Flutter', 'Release.xcconfig'));
+      podfile = fs.file(fs.path.join('project', 'ios', 'Podfile'));
+    });
 
-  testUsingContext(
-    'do not recreate Podfile when present',
-    () async {
-      fs.file(fs.path.join('project', 'ios', 'Podfile'))
-        ..createSync()
-        ..writeAsString('Existing Podfile');
-      await cocoaPodsUnderTest.processPods(
-        appIosDir: projectUnderTest,
-        iosEngineDir: 'engine/path',
-      );
-      expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile');
-      verify(mockProcessManager.run(
-        <String>['pod', 'install', '--verbose'],
-        workingDirectory: 'project/ios',
-        environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
-      ));
-    },
-    overrides: <Type, Generator>{
-      FileSystem: () => fs,
-      ProcessManager: () => mockProcessManager,
-    },
-  );
+    testUsingContext('creates objective-c Podfile when not present', () {
+      cocoaPodsUnderTest.setupPodfile('project');
 
-  testUsingContext(
-    'missing CocoaPods throws',
-    () async {
+      expect(podfile.readAsStringSync(), 'Objective-C podfile template');
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+
+    testUsingContext('creates swift Podfile if swift', () {
+      when(mockXcodeProjectInterpreter.canInterpretXcodeProjects).thenReturn(true);
+      when(mockXcodeProjectInterpreter.getBuildSettings(any, any)).thenReturn(<String, String>{
+        'SWIFT_VERSION': '4.0',
+      });
+
+      cocoaPodsUnderTest.setupPodfile('project');
+
+      expect(podfile.readAsStringSync(), 'Swift podfile template');
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
+    });
+
+    testUsingContext('does not recreate Podfile when already present', () {
+      podfile..createSync()..writeAsStringSync('Existing Podfile');
+
+      cocoaPodsUnderTest.setupPodfile('project');
+
+      expect(podfile.readAsStringSync(), 'Existing Podfile');
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+
+    testUsingContext('does not create Podfile when we cannot interpret Xcode projects', () {
+      when(mockXcodeProjectInterpreter.canInterpretXcodeProjects).thenReturn(false);
+
+      cocoaPodsUnderTest.setupPodfile('project');
+
+      expect(podfile.existsSync(), false);
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
+    });
+
+    testUsingContext('includes Pod config in xcconfig files, if not present', () {
+      podfile..createSync()..writeAsStringSync('Existing Podfile');
+      debugConfigFile..createSync(recursive: true)..writeAsStringSync('Existing debug config');
+      releaseConfigFile..createSync(recursive: true)..writeAsStringSync('Existing release config');
+
+      cocoaPodsUnderTest.setupPodfile('project');
+
+      final String debugContents = debugConfigFile.readAsStringSync();
+      expect(debugContents, contains(
+          '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"\n'));
+      expect(debugContents, contains('Existing debug config'));
+      final String releaseContents = releaseConfigFile.readAsStringSync();
+      expect(releaseContents, contains(
+          '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"\n'));
+      expect(releaseContents, contains('Existing release config'));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+    });
+  });
+
+  group('Process pods', () {
+    testUsingContext('prints error, if CocoaPods is not installed', () async {
+      projectUnderTest.childFile('Podfile').createSync();
       cocoaPodsUnderTest = const TestCocoaPods(false);
+      await cocoaPodsUnderTest.processPods(
+        appIosDir: projectUnderTest,
+        iosEngineDir: 'engine/path',
+      );
+      verifyNever(mockProcessManager.run(
+        typed<List<String>>(any),
+        workingDirectory: any,
+        environment: typed<Map<String, String>>(any, named: 'environment'),
+      ));
+      expect(testLogger.errorText, contains('not installed'));
+      expect(testLogger.errorText, contains('Skipping pod install'));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext('throws, if Podfile is missing.', () async {
+      cocoaPodsUnderTest = const TestCocoaPods(true);
       try {
         await cocoaPodsUnderTest.processPods(
           appIosDir: projectUnderTest,
           iosEngineDir: 'engine/path',
         );
-        fail('Expected tool error');
-      } catch (ToolExit) {
+        fail('ToolExit expected');
+      } catch(e) {
+        expect(e, const isInstanceOf<ToolExit>());
         verifyNever(mockProcessManager.run(
-          <String>['pod', 'install', '--verbose'],
-          workingDirectory: 'project/ios',
-          environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
+          typed<List<String>>(any),
+          workingDirectory: any,
+          environment: typed<Map<String, String>>(any, named: 'environment'),
         ));
       }
-    },
-    overrides: <Type, Generator>{
+    }, overrides: <Type, Generator>{
       FileSystem: () => fs,
       ProcessManager: () => mockProcessManager,
-    },
-  );
+    });
 
-  testUsingContext(
-    'outdated specs repo should print error',
-    () async {
+    testUsingContext('throws, if specs repo is outdated.', () async {
       fs.file(fs.path.join('project', 'ios', 'Podfile'))
         ..createSync()
         ..writeAsString('Existing Podfile');
@@ -143,7 +173,10 @@
       when(mockProcessManager.run(
         <String>['pod', 'install', '--verbose'],
         workingDirectory: 'project/ios',
-        environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
+        environment: <String, String>{
+          'FLUTTER_FRAMEWORK_DIR': 'engine/path',
+          'COCOAPODS_DISABLE_STATS': 'true',
+        },
       )).thenReturn(new ProcessResult(
         1,
         1,
@@ -167,78 +200,152 @@
         await cocoaPodsUnderTest.processPods(
           appIosDir: projectUnderTest,
           iosEngineDir: 'engine/path',
-        );      expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile');
-        fail('Exception expected');
-      } catch (ToolExit) {
-        expect(testLogger.errorText, contains("CocoaPods's specs repository is too out-of-date to satisfy dependencies"));
+        );
+        fail('ToolExit expected');
+      } catch (e) {
+        expect(e, const isInstanceOf<ToolExit>());
+        expect(
+          testLogger.errorText,
+          contains("CocoaPods's specs repository is too out-of-date to satisfy dependencies"),
+        );
       }
-    },
-    overrides: <Type, Generator>{
+    }, overrides: <Type, Generator>{
       FileSystem: () => fs,
       ProcessManager: () => mockProcessManager,
-    },
-  );
+    });
 
-  testUsingContext(
-    'Run pod install if plugins or flutter framework have changes.',
-        () async {
-      fs.file(fs.path.join('project', 'ios', 'Podfile'))
+    testUsingContext('run pod install, if Podfile.lock is missing', () async {
+      projectUnderTest.childFile('Podfile')
         ..createSync()
         ..writeAsString('Existing Podfile');
-      fs.file(fs.path.join('project', 'ios', 'Podfile.lock'))
-        ..createSync()
-        ..writeAsString('Existing lock files.');
-      fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock'))
+      projectUnderTest.childFile('Pods/Manifest.lock')
         ..createSync(recursive: true)
-        ..writeAsString('Existing lock files.');
+        ..writeAsString('Existing lock file.');
       await cocoaPodsUnderTest.processPods(
-          appIosDir: projectUnderTest,
-          iosEngineDir: 'engine/path',
-          pluginOrFlutterPodChanged: true
+        appIosDir: projectUnderTest,
+        iosEngineDir: 'engine/path',
+        flutterPodChanged: false,
       );
       verify(mockProcessManager.run(
         <String>['pod', 'install', '--verbose'],
         workingDirectory: 'project/ios',
         environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
       ));
-    },
-    overrides: <Type, Generator>{
+    }, overrides: <Type, Generator>{
       FileSystem: () => fs,
       ProcessManager: () => mockProcessManager,
-    },
-  );
+    });
 
-  testUsingContext(
-    'Skip pod install if plugins and flutter framework remain unchanged.',
-        () async {
-      fs.file(fs.path.join('project', 'ios', 'Podfile'))
+    testUsingContext('runs pod install, if Manifest.lock is missing', () async {
+      projectUnderTest.childFile('Podfile')
         ..createSync()
         ..writeAsString('Existing Podfile');
-      fs.file(fs.path.join('project', 'ios', 'Podfile.lock'))
+      projectUnderTest.childFile('Podfile.lock')
         ..createSync()
-        ..writeAsString('Existing lock files.');
-      fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock'))
-        ..createSync(recursive: true)
-        ..writeAsString('Existing lock files.');
+        ..writeAsString('Existing lock file.');
       await cocoaPodsUnderTest.processPods(
-          appIosDir: projectUnderTest,
-          iosEngineDir: 'engine/path',
-          pluginOrFlutterPodChanged: false
+        appIosDir: projectUnderTest,
+        iosEngineDir: 'engine/path',
+        flutterPodChanged: false,
       );
-      verifyNever(mockProcessManager.run(
+      verify(mockProcessManager.run(
         <String>['pod', 'install', '--verbose'],
         workingDirectory: 'project/ios',
-        environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'},
+        environment: <String, String>{
+          'FLUTTER_FRAMEWORK_DIR': 'engine/path',
+          'COCOAPODS_DISABLE_STATS': 'true',
+        },
       ));
-    },
-    overrides: <Type, Generator>{
+    }, overrides: <Type, Generator>{
       FileSystem: () => fs,
       ProcessManager: () => mockProcessManager,
-    },
-  );
+    });
+
+    testUsingContext('runs pod install, if Manifest.lock different from Podspec.lock', () async {
+      projectUnderTest.childFile('Podfile')
+        ..createSync()
+        ..writeAsString('Existing Podfile');
+      projectUnderTest.childFile('Podfile.lock')
+        ..createSync()
+        ..writeAsString('Existing lock file.');
+      projectUnderTest.childFile('Pods/Manifest.lock')
+        ..createSync(recursive: true)
+        ..writeAsString('Different lock file.');
+      await cocoaPodsUnderTest.processPods(
+        appIosDir: projectUnderTest,
+        iosEngineDir: 'engine/path',
+        flutterPodChanged: false,
+      );
+      verify(mockProcessManager.run(
+        <String>['pod', 'install', '--verbose'],
+        workingDirectory: 'project/ios',
+        environment: <String, String>{
+          'FLUTTER_FRAMEWORK_DIR': 'engine/path',
+          'COCOAPODS_DISABLE_STATS': 'true',
+        },
+      ));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext('runs pod install, if flutter framework changed', () async {
+      projectUnderTest.childFile('Podfile')
+        ..createSync()
+        ..writeAsString('Existing Podfile');
+      projectUnderTest.childFile('Podfile.lock')
+        ..createSync()
+        ..writeAsString('Existing lock file.');
+      projectUnderTest.childFile('Pods/Manifest.lock')
+        ..createSync(recursive: true)
+        ..writeAsString('Existing lock file.');
+      await cocoaPodsUnderTest.processPods(
+        appIosDir: projectUnderTest,
+        iosEngineDir: 'engine/path',
+        flutterPodChanged: true,
+      );
+      verify(mockProcessManager.run(
+        <String>['pod', 'install', '--verbose'],
+        workingDirectory: 'project/ios',
+        environment: <String, String>{
+          'FLUTTER_FRAMEWORK_DIR': 'engine/path',
+          'COCOAPODS_DISABLE_STATS': 'true',
+        },
+      ));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      ProcessManager: () => mockProcessManager,
+    });
+
+    testUsingContext('skips pod install, if nothing changed', () async {
+      projectUnderTest.childFile('Podfile')
+        ..createSync()
+        ..writeAsString('Existing Podfile');
+      projectUnderTest.childFile('Podfile.lock')
+        ..createSync()
+        ..writeAsString('Existing lock file.');
+      projectUnderTest.childFile('Pods/Manifest.lock')
+        ..createSync(recursive: true)
+        ..writeAsString('Existing lock file.');
+      await cocoaPodsUnderTest.processPods(
+        appIosDir: projectUnderTest,
+        iosEngineDir: 'engine/path',
+        flutterPodChanged: false,
+      );
+      verifyNever(mockProcessManager.run(
+        typed<List<String>>(any),
+        workingDirectory: any,
+        environment: typed<Map<String, String>>(any, named: 'environment'),
+      ));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      ProcessManager: () => mockProcessManager,
+    });
+  });
 }
 
 class MockProcessManager extends Mock implements ProcessManager {}
+class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
 
 class TestCocoaPods extends CocoaPods {
   const TestCocoaPods([this._hasCocoaPods = true]);
diff --git a/packages/flutter_tools/test/project_test.dart b/packages/flutter_tools/test/project_test.dart
index 2f1a3ff..6ef5bbb 100644
--- a/packages/flutter_tools/test/project_test.dart
+++ b/packages/flutter_tools/test/project_test.dart
@@ -17,6 +17,29 @@
       final Directory directory = fs.directory('myproject');
       expect(new FlutterProject(directory).directory, directory);
     });
+    group('ensure ready for platform-specific tooling', () {
+      testInMemory('does nothing, if project is not created', () async {
+        final FlutterProject project = someProject();
+        project.ensureReadyForPlatformSpecificTooling();
+        expect(project.directory.existsSync(), isFalse);
+      });
+      testInMemory('injects plugins', () async {
+        final FlutterProject project = aProjectWithIos();
+        project.ensureReadyForPlatformSpecificTooling();
+        expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isTrue);
+      });
+      testInMemory('generates Xcode configuration', () async {
+        final FlutterProject project = aProjectWithIos();
+        project.ensureReadyForPlatformSpecificTooling();
+        expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isTrue);
+      });
+      testInMemory('generates files in plugin example project', () async {
+        final FlutterProject project = aPluginProject();
+        project.ensureReadyForPlatformSpecificTooling();
+        expect(project.example.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isTrue);
+        expect(project.example.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isTrue);
+      });
+    });
     group('organization names set', () {
       testInMemory('is empty, if project not created', () async {
         final FlutterProject project = someProject();
@@ -71,8 +94,23 @@
   });
 }
 
-FlutterProject someProject() =>
-    new FlutterProject(fs.directory('some_project'));
+FlutterProject someProject() => new FlutterProject(fs.directory('some_project'));
+
+FlutterProject aProjectWithIos() {
+  final Directory directory = fs.directory('ios_project');
+  directory.childFile('pubspec.yaml').createSync(recursive: true);
+  directory.childFile('.packages').createSync(recursive: true);
+  directory.childDirectory('ios').createSync(recursive: true);
+  return new FlutterProject(directory);
+}
+
+FlutterProject aPluginProject() {
+  final Directory directory = fs.directory('plugin_project/example');
+  directory.childFile('pubspec.yaml').createSync(recursive: true);
+  directory.childFile('.packages').createSync(recursive: true);
+  directory.childDirectory('ios').createSync(recursive: true);
+  return new FlutterProject(directory.parent);
+}
 
 void testInMemory(String description, Future<Null> testMethod()) {
   testUsingContext(
@@ -84,6 +122,13 @@
   );
 }
 
+void addPubPackages(Directory directory) {
+  directory.childFile('pubspec.yaml')
+    ..createSync(recursive: true);
+  directory.childFile('.packages')
+    ..createSync(recursive: true);
+}
+
 void addIosWithBundleId(Directory directory, String id) {
   directory
       .childDirectory('ios')
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart
index 3c5fe1b..dbe5188 100644
--- a/packages/flutter_tools/test/src/context.dart
+++ b/packages/flutter_tools/test/src/context.dart
@@ -19,6 +19,7 @@
 import 'package:flutter_tools/src/doctor.dart';
 import 'package:flutter_tools/src/ios/mac.dart';
 import 'package:flutter_tools/src/ios/simulators.dart';
+import 'package:flutter_tools/src/ios/xcodeproj.dart';
 import 'package:flutter_tools/src/run_hot.dart';
 import 'package:flutter_tools/src/usage.dart';
 import 'package:flutter_tools/src/version.dart';
@@ -50,6 +51,7 @@
     ..putIfAbsent(OperatingSystemUtils, () => new MockOperatingSystemUtils())
     ..putIfAbsent(PortScanner, () => new MockPortScanner())
     ..putIfAbsent(Xcode, () => new Xcode())
+    ..putIfAbsent(XcodeProjectInterpreter, () => new MockXcodeProjectInterpreter())
     ..putIfAbsent(IOSSimulatorUtils, () {
       final MockIOSSimulatorUtils mock = new MockIOSSimulatorUtils();
       when(mock.getAttachedDevices()).thenReturn(<IOSSimulator>[]);
@@ -262,6 +264,25 @@
   void printWelcome() { }
 }
 
+class MockXcodeProjectInterpreter implements XcodeProjectInterpreter {
+  @override
+  bool get canInterpretXcodeProjects => true;
+
+  @override
+  Map<String, String> getBuildSettings(String projectPath, String target) {
+    return <String, String>{};
+  }
+
+  @override
+  XcodeProjectInfo getInfo(String projectPath) {
+    return new XcodeProjectInfo(
+      <String>['Runner'],
+      <String>['Debug', 'Release'],
+      <String>['Runner'],
+    );
+  }
+}
+
 class MockFlutterVersion extends Mock implements FlutterVersion {}
 
 class MockClock extends Mock implements Clock {}