Create Podfile dynamically part 1 (#11101)

* start

* with create

* refactor cocoapod code, add tests

* fix tests

* throw when cocoapod missing

* obj-c projects don’t use use_framework!
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 4e724c0..a931d36 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -224,6 +224,9 @@
   @override
   String get deviceBundlePath => _buildAppPath('iphoneos');
 
+  /// True if the app is built from a Swift project. Null if unknown.
+  bool get isSwift => buildSettings?.containsKey('SWIFT_VERSION');
+
   String _buildAppPath(String type) {
     return fs.path.join(getIosBuildDirectory(), 'Release-$type', kBundleName);
   }
diff --git a/packages/flutter_tools/lib/src/ios/cocoapods.dart b/packages/flutter_tools/lib/src/ios/cocoapods.dart
new file mode 100644
index 0000000..5b00037
--- /dev/null
+++ b/packages/flutter_tools/lib/src/ios/cocoapods.dart
@@ -0,0 +1,136 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+
+import '../base/common.dart';
+import '../base/context.dart';
+import '../base/file_system.dart';
+import '../base/io.dart';
+import '../base/logger.dart';
+import '../base/process.dart';
+import '../base/process_manager.dart';
+import '../base/version.dart';
+import '../cache.dart';
+import '../globals.dart';
+
+final String noCocoaPodsConsequence = '''
+  CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side.
+  Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS.
+  For more info, see https://flutter.io/platform-plugins''';
+
+final String cocoaPodsInstallInstructions = '''
+  brew update
+  brew install cocoapods
+  pod setup''';
+
+final String cocoaPodsUpgradeInstructions = '''
+  brew update
+  brew upgrade cocoapods
+  pod setup''';
+
+CocoaPods get cocoaPods => context.putIfAbsent(CocoaPods, () => const CocoaPods());
+
+class CocoaPods {
+  const CocoaPods();
+
+  Future<bool> get hasCocoaPods => exitsHappyAsync(<String>['pod', '--version']);
+
+  String get cocoaPodsMinimumVersion => '1.0.0';
+
+  Future<String> get cocoaPodsVersionText async => (await runAsync(<String>['pod', '--version'])).processResult.stdout.trim();
+
+  Future<bool> get isCocoaPodsInstalledAndMeetsVersionCheck async {
+    if (!await hasCocoaPods)
+      return false;
+    try {
+      final Version installedVersion = new Version.parse(await cocoaPodsVersionText);
+      return installedVersion >= new Version.parse(cocoaPodsMinimumVersion);
+    } on FormatException {
+      return false;
+    }
+  }
+
+  /// Whether CocoaPods ran 'pod setup' once where the costly pods' specs are cloned.
+  Future<bool> get isCocoaPodsInitialized => fs.isDirectory(fs.path.join(homeDirPath, '.cocoapods', 'repos', 'master'));
+
+  Future<Null> processPods({
+    @required Directory appIosDir,
+    @required String iosEngineDir,
+    bool isSwift: false,
+  }) async {
+    if (await _checkPodCondition()) {
+      if (!fs.file(fs.path.join(appIosDir.path, 'Podfile')).existsSync()) {
+        await _createPodfile(appIosDir, isSwift);
+      } // TODO(xster): Add more logic for handling merge conflicts.
+
+      await _runPodInstall(appIosDir, iosEngineDir);
+    } else {
+      throwToolExit('CocoaPods not available for project using Flutter plugins');
+    }
+  }
+
+  Future<bool> _checkPodCondition() async {
+    if (!await isCocoaPodsInstalledAndMeetsVersionCheck) {
+      final String minimumVersion = cocoaPodsMinimumVersion;
+      printError(
+        'Warning: CocoaPods version $minimumVersion or greater not installed. Skipping pod install.\n'
+        '$noCocoaPodsConsequence\n'
+        'To install:\n'
+        '$cocoaPodsInstallInstructions\n',
+        emphasis: true,
+      );
+      return false;
+    }
+    if (!await isCocoaPodsInitialized) {
+      printError(
+        'Warning: CocoaPods installed but not initialized. Skipping pod install.\n'
+        '$noCocoaPodsConsequence\n'
+        'To initialize CocoaPods, run:\n'
+        '  pod setup\n'
+        'once to finalize CocoaPods\' installation.',
+        emphasis: true,
+      );
+      return false;
+    }
+
+    return true;
+  }
+
+  Future<Null> _createPodfile(Directory bundle, bool isSwift) async {
+    final File podfileTemplate = fs.file(fs.path.join(
+      Cache.flutterRoot,
+      'packages',
+      'flutter_tools',
+      'templates',
+      'cocoapods',
+      isSwift ? 'Podfile-swift' : 'Podfile-objc',
+    ));
+    podfileTemplate.copySync(fs.path.join(bundle.path, 'Podfile'));
+  }
+
+  Future<Null> _runPodInstall(Directory bundle, String engineDirectory) async {
+    final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true);
+    final ProcessResult result = await processManager.run(
+      <String>['pod', 'install', '--verbose'],
+      workingDirectory: bundle.path,
+      environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': engineDirectory},
+    );
+    status.stop();
+    if (logger.isVerbose || result.exitCode != 0) {
+      if (result.stdout.isNotEmpty) {
+        printStatus('CocoaPods\' output:\n↳');
+        printStatus(result.stdout, indent: 4);
+      }
+      if (result.stderr.isNotEmpty) {
+        printStatus('Error output from CocoaPods:\n↳');
+        printStatus(result.stderr, indent: 4);
+      }
+    }
+    if (result.exitCode != 0)
+      throwToolExit('Error running pod install');
+  }
+}
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
index ee87270..a833ef5 100644
--- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -4,14 +4,13 @@
 
 import 'dart:async';
 
-import '../base/common.dart';
 import '../base/context.dart';
-import '../base/file_system.dart';
 import '../base/os.dart';
 import '../base/platform.dart';
 import '../base/process.dart';
 import '../base/version.dart';
 import '../doctor.dart';
+import 'cocoapods.dart';
 import 'mac.dart';
 
 IOSWorkflow get iosWorkflow => context.putIfAbsent(IOSWorkflow, () => new IOSWorkflow());
@@ -43,12 +42,6 @@
 
   bool get hasPythonSixModule => kPythonSix.isInstalled;
 
-  Future<bool> get hasCocoaPods => exitsHappyAsync(<String>['pod', '--version']);
-
-  String get cocoaPodsMinimumVersion => '1.0.0';
-
-  Future<String> get cocoaPodsVersionText async => (await runAsync(<String>['pod', '--version'])).processResult.stdout.trim();
-
   Future<String> get macDevMode async => (await runAsync(<String>['DevToolsSecurity', '-status'])).processResult.stdout;
 
   Future<bool> get _iosDeployIsInstalledAndMeetsVersionCheck async {
@@ -62,20 +55,6 @@
     }
   }
 
-  Future<bool> get isCocoaPodsInstalledAndMeetsVersionCheck async {
-    if (!await hasCocoaPods)
-      return false;
-    try {
-      final Version installedVersion = new Version.parse(await cocoaPodsVersionText);
-      return installedVersion >= new Version.parse(cocoaPodsMinimumVersion);
-    } on FormatException {
-      return false;
-    }
-  }
-
-  /// Whether CocoaPods ran 'pod setup' once where the costly pods' specs are cloned.
-  Future<bool> get isCocoaPodsInitialized => fs.isDirectory(fs.path.join(homeDirPath, '.cocoapods', 'repos', 'master'));
-
   @override
   Future<ValidationResult> validate() async {
     final List<ValidationMessage> messages = <ValidationMessage>[];
@@ -187,9 +166,9 @@
         }
       }
 
-      if (await isCocoaPodsInstalledAndMeetsVersionCheck) {
-        if (await isCocoaPodsInitialized) {
-          messages.add(new ValidationMessage('CocoaPods version ${await cocoaPodsVersionText}'));
+      if (await cocoaPods.isCocoaPodsInstalledAndMeetsVersionCheck) {
+        if (await cocoaPods.isCocoaPodsInitialized) {
+          messages.add(new ValidationMessage('CocoaPods version ${await cocoaPods.cocoaPodsVersionText}'));
         } else {
           brewStatus = ValidationType.partial;
           messages.add(new ValidationMessage.error(
@@ -202,7 +181,7 @@
         }
       } else {
         brewStatus = ValidationType.partial;
-        if (!await hasCocoaPods) {
+        if (!await cocoaPods.hasCocoaPods) {
           messages.add(new ValidationMessage.error(
             'CocoaPods not installed.\n'
             '$noCocoaPodsConsequence\n'
@@ -211,7 +190,7 @@
           ));
         } else {
           messages.add(new ValidationMessage.error(
-            'CocoaPods out of date ($cocoaPodsMinimumVersion is required).\n'
+            'CocoaPods out of date ($cocoaPods.cocoaPodsMinimumVersion is required).\n'
             '$noCocoaPodsConsequence\n'
             'To upgrade:\n'
             '$cocoaPodsUpgradeInstructions'
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index a4ea001..8fb6ef1 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -21,8 +21,8 @@
 import '../globals.dart';
 import '../plugins.dart';
 import '../services.dart';
+import 'cocoapods.dart';
 import 'code_signing.dart';
-import 'ios_workflow.dart';
 import 'xcodeproj.dart';
 
 const int kXcodeRequiredVersionMajor = 8;
@@ -215,7 +215,11 @@
   final bool hasFlutterPlugins = injectPlugins();
 
   if (hasFlutterPlugins)
-    await _runPodInstall(appDirectory, flutterFrameworkDir(mode));
+    await cocoaPods.processPods(
+      appIosDir: appDirectory,
+      iosEngineDir: flutterFrameworkDir(mode),
+      isSwift: app.isSwift,
+    );
 
   updateXcodeGeneratedProperties(
     projectPath: fs.currentDirectory.path,
@@ -396,67 +400,6 @@
   return true;
 }
 
-final String noCocoaPodsConsequence = '''
-  CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side.
-  Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS.
-  For more info, see https://flutter.io/platform-plugins''';
-
-final String cocoaPodsInstallInstructions = '''
-  brew update
-  brew install cocoapods
-  pod setup''';
-
-final String cocoaPodsUpgradeInstructions = '''
-  brew update
-  brew upgrade cocoapods
-  pod setup''';
-
-Future<Null> _runPodInstall(Directory bundle, String engineDirectory) async {
-  if (fs.file(fs.path.join(bundle.path, 'Podfile')).existsSync()) {
-    if (!await iosWorkflow.isCocoaPodsInstalledAndMeetsVersionCheck) {
-      final String minimumVersion = iosWorkflow.cocoaPodsMinimumVersion;
-      printError(
-        'Warning: CocoaPods version $minimumVersion or greater not installed. Skipping pod install.\n'
-        '$noCocoaPodsConsequence\n'
-        'To install:\n'
-        '$cocoaPodsInstallInstructions\n',
-        emphasis: true,
-      );
-      return;
-    }
-    if (!await iosWorkflow.isCocoaPodsInitialized) {
-      printError(
-        'Warning: CocoaPods installed but not initialized. Skipping pod install.\n'
-        '$noCocoaPodsConsequence\n'
-        'To initialize CocoaPods, run:\n'
-        '  pod setup\n'
-        'once to finalize CocoaPods\' installation.',
-        emphasis: true,
-      );
-      return;
-    }
-    final Status status = logger.startProgress('Running pod install...', expectSlowOperation: true);
-    final ProcessResult result = await processManager.run(
-      <String>['pod', 'install', '--verbose'],
-      workingDirectory: bundle.path,
-      environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': engineDirectory},
-    );
-    status.stop();
-    if (logger.isVerbose || result.exitCode != 0) {
-      if (result.stdout.isNotEmpty) {
-        printStatus('CocoaPods\' output:\n↳');
-        printStatus(result.stdout, indent: 4);
-      }
-      if (result.stderr.isNotEmpty) {
-        printStatus('Error output from CocoaPods:\n↳');
-        printStatus(result.stderr, indent: 4);
-      }
-    }
-    if (result.exitCode != 0)
-      throwToolExit('Error running pod install');
-  }
-}
-
 Future<Null> _addServicesToBundle(Directory bundle) async {
   final List<Map<String, String>> services = <Map<String, String>>[];
   printTrace("Trying to resolve native pub services.");
diff --git a/packages/flutter_tools/templates/create/ios-objc.tmpl/Podfile b/packages/flutter_tools/templates/cocoapods/Podfile-objc
similarity index 100%
rename from packages/flutter_tools/templates/create/ios-objc.tmpl/Podfile
rename to packages/flutter_tools/templates/cocoapods/Podfile-objc
diff --git a/packages/flutter_tools/templates/create/ios-swift.tmpl/Podfile b/packages/flutter_tools/templates/cocoapods/Podfile-swift
similarity index 100%
rename from packages/flutter_tools/templates/create/ios-swift.tmpl/Podfile
rename to packages/flutter_tools/templates/cocoapods/Podfile-swift
diff --git a/packages/flutter_tools/test/application_package_test.dart b/packages/flutter_tools/test/application_package_test.dart
index 12aa8ba..b46a58a 100644
--- a/packages/flutter_tools/test/application_package_test.dart
+++ b/packages/flutter_tools/test/application_package_test.dart
@@ -16,6 +16,17 @@
       expect(data.launchableActivityName, 'io.flutter.app.FlutterActivity');
     });
   });
+
+  group('BuildableIOSApp', () {
+    testUsingContext('check isSwift', () {
+      final BuildableIOSApp buildableIOSApp = new BuildableIOSApp(
+        projectBundleId: 'blah',
+        appDirectory: 'not/important',
+        buildSettings: _swiftBuildSettings,
+      );
+      expect(buildableIOSApp.isSwift, true);
+    });
+  });
 }
 
 final String _aaptData = '''
@@ -44,3 +55,16 @@
 densities: '160' '240' '320' '480' '640'
 native-code: 'armeabi-v7a'
 ''';
+
+final Map<String, String> _swiftBuildSettings = <String, String>{
+  'ARCHS': 'arm64',
+  'ASSETCATALOG_COMPILER_APPICON_NAME': 'AppIcon',
+  'CLANG_ENABLE_MODULES': 'YES',
+  'ENABLE_BITCODE': 'NO',
+  'INFOPLIST_FILE': 'Runner/Info.plist',
+  'PRODUCT_BUNDLE_IDENTIFIER': 'com.yourcompany.test',
+  'PRODUCT_NAME': 'blah',
+  'SWIFT_OBJC_BRIDGING_HEADER': 'Runner/Runner-Bridging-Header.h',
+  'SWIFT_OPTIMIZATION_LEVEL': '-Onone',
+  'SWIFT_VERSION': '3.0',
+};
diff --git a/packages/flutter_tools/test/ios/cocoapods_test.dart b/packages/flutter_tools/test/ios/cocoapods_test.dart
new file mode 100644
index 0000000..615b052
--- /dev/null
+++ b/packages/flutter_tools/test/ios/cocoapods_test.dart
@@ -0,0 +1,158 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:file/file.dart';
+import 'package:file/memory.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:mockito/mockito.dart';
+import 'package:process/process.dart';
+import 'package:test/test.dart';
+
+import '../src/context.dart';
+
+void main() {
+  FileSystem fs;
+  ProcessManager mockProcessManager;
+  Directory projectUnderTest;
+  CocoaPods cocoaPodsUnderTest;
+
+  setUp(() {
+    Cache.flutterRoot = 'flutter';
+    fs = new MemoryFileSystem();
+    mockProcessManager = new MockProcessManager();
+    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'
+    ))
+        ..createSync(recursive: true)
+        ..writeAsStringSync('Objective-C podfile template');
+    fs.file(fs.path.join(
+      Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-swift'
+    ))
+        ..createSync(recursive: true)
+        ..writeAsStringSync('Swift podfile template');
+    cocoaPodsUnderTest = const TestCocoaPods();
+
+    when(mockProcessManager.run(
+      <String>['pod', 'install', '--verbose'],
+      workingDirectory: 'project/ios',
+      environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path'},
+    )).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'},
+      ));
+    },
+    overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      ProcessManager: () => mockProcessManager,
+    },
+  );
+
+  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'},
+      ));
+    },
+    overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      ProcessManager: () => mockProcessManager,
+    },
+  );
+
+  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'},
+      ));
+    },
+    overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      ProcessManager: () => mockProcessManager,
+    },
+  );
+
+  testUsingContext(
+    'missing CocoaPods throws',
+    () async {
+      cocoaPodsUnderTest = const TestCocoaPods(false);
+      try {
+        await cocoaPodsUnderTest.processPods(
+          appIosDir: projectUnderTest,
+          iosEngineDir: 'engine/path',
+        );
+        fail('Expected tool error');
+      } catch (ToolExit) {
+        verifyNever(mockProcessManager.run(
+          <String>['pod', 'install', '--verbose'],
+          workingDirectory: 'project/ios',
+          environment: <String, String>{'FLUTTER_FRAMEWORK_DIR': 'engine/path'},
+        ));
+      }
+    },
+    overrides: <Type, Generator>{
+      FileSystem: () => fs,
+      ProcessManager: () => mockProcessManager,
+    },
+  );
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
+
+class TestCocoaPods extends CocoaPods {
+  const TestCocoaPods([this._hasCocoaPods = true]);
+
+  final bool _hasCocoaPods;
+
+  @override
+  Future<bool> get hasCocoaPods => new Future<bool>.value(_hasCocoaPods);
+
+  @override
+  Future<String> get cocoaPodsVersionText async => new Future<String>.value('1.5.0');
+
+  @override
+  Future<bool> get isCocoaPodsInitialized => new Future<bool>.value(true);
+}
+
+final ProcessResult exitsHappy = new ProcessResult(
+  1,     // pid
+  0,     // exitCode
+  '',    // stdout
+  '',    // stderr
+);
\ No newline at end of file
diff --git a/packages/flutter_tools/test/ios/ios_workflow_test.dart b/packages/flutter_tools/test/ios/ios_workflow_test.dart
index cf92c7e..5cb1902 100644
--- a/packages/flutter_tools/test/ios/ios_workflow_test.dart
+++ b/packages/flutter_tools/test/ios/ios_workflow_test.dart
@@ -9,6 +9,7 @@
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/doctor.dart';
+import 'package:flutter_tools/src/ios/cocoapods.dart';
 import 'package:flutter_tools/src/ios/ios_workflow.dart';
 import 'package:flutter_tools/src/ios/mac.dart';
 import 'package:mockito/mockito.dart';
@@ -22,13 +23,18 @@
     MockIMobileDevice iMobileDevice;
     MockXcode xcode;
     MockProcessManager processManager;
+    MockCocoaPods cocoaPods;
     FileSystem fs;
 
     setUp(() {
       iMobileDevice = new MockIMobileDevice();
       xcode = new MockXcode();
       processManager = new MockProcessManager();
+      cocoaPods = new MockCocoaPods();
       fs = new MemoryFileSystem();
+
+      when(cocoaPods.isCocoaPodsInstalledAndMeetsVersionCheck).thenReturn(true);
+      when(cocoaPods.isCocoaPodsInitialized).thenReturn(true);
     });
 
     testUsingContext('Emit missing status when nothing is installed', () async {
@@ -44,6 +50,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when Xcode is not installed', () async {
@@ -55,6 +62,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when Xcode is partially installed', () async {
@@ -66,6 +74,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when Xcode version too low', () async {
@@ -80,6 +89,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when Xcode EULA not signed', () async {
@@ -94,6 +104,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when Mac dev mode was never enabled', () async {
@@ -108,6 +119,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when python six not installed', () async {
@@ -122,6 +134,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when homebrew not installed', () async {
@@ -136,6 +149,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when libimobiledevice is not installed', () async {
@@ -150,6 +164,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => new MockIMobileDevice(isWorking: false),
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when ios-deploy is not installed', () async {
@@ -164,6 +179,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when ios-deploy version is too low', () async {
@@ -178,6 +194,7 @@
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when CocoaPods is not installed', () async {
@@ -186,12 +203,15 @@
           .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
       when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
       when(xcode.eulaSigned).thenReturn(true);
-      final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget(hasCocoaPods: false);
+      when(cocoaPods.isCocoaPodsInstalledAndMeetsVersionCheck).thenReturn(false);
+      when(cocoaPods.hasCocoaPods).thenReturn(false);
+      final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
       final ValidationResult result = await workflow.validate();
       expect(result.type, ValidationType.partial);
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when CocoaPods version is too low', () async {
@@ -200,12 +220,16 @@
           .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
       when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
       when(xcode.eulaSigned).thenReturn(true);
-      final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget(cocoaPodsVersionText: '0.39.0');
+      when(cocoaPods.isCocoaPodsInstalledAndMeetsVersionCheck).thenReturn(false);
+      when(cocoaPods.hasCocoaPods).thenReturn(true);
+      when(cocoaPods.cocoaPodsVersionText).thenReturn('0.39.0');
+      final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
       final ValidationResult result = await workflow.validate();
       expect(result.type, ValidationType.partial);
     }, overrides: <Type, Generator>{
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
     });
 
     testUsingContext('Emits partial status when CocoaPods is not initialized', () async {
@@ -214,6 +238,9 @@
           .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
       when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
       when(xcode.eulaSigned).thenReturn(true);
+      when(cocoaPods.isCocoaPodsInstalledAndMeetsVersionCheck).thenReturn(false);
+      when(cocoaPods.hasCocoaPods).thenReturn(true);
+      when(cocoaPods.isCocoaPodsInitialized).thenReturn(false);
 
       final ValidationResult result = await new IOSWorkflowTestTarget().validate();
       expect(result.type, ValidationType.partial);
@@ -221,6 +248,7 @@
       FileSystem: () => fs,
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
       ProcessManager: () => processManager,
     });
 
@@ -239,6 +267,7 @@
       FileSystem: () => fs,
       IMobileDevice: () => iMobileDevice,
       Xcode: () => xcode,
+      CocoaPods: () => cocoaPods,
       ProcessManager: () => processManager,
     });
   });
@@ -260,6 +289,7 @@
 
 class MockXcode extends Mock implements Xcode {}
 class MockProcessManager extends Mock implements ProcessManager {}
+class MockCocoaPods extends Mock implements CocoaPods {}
 
 class IOSWorkflowTestTarget extends IOSWorkflow {
   IOSWorkflowTestTarget({
@@ -268,14 +298,10 @@
     bool hasIosDeploy: true,
     String iosDeployVersionText: '1.9.0',
     bool hasIDeviceInstaller: true,
-    bool hasCocoaPods: true,
-    String cocoaPodsVersionText: '1.2.0',
     String macDevMode: 'Developer mode is already enabled.',
   }) : hasIosDeploy = new Future<bool>.value(hasIosDeploy),
        iosDeployVersionText = new Future<String>.value(iosDeployVersionText),
        hasIDeviceInstaller = new Future<bool>.value(hasIDeviceInstaller),
-       hasCocoaPods = new Future<bool>.value(hasCocoaPods),
-       cocoaPodsVersionText = new Future<String>.value(cocoaPodsVersionText),
        macDevMode = new Future<String>.value(macDevMode);
 
   @override
@@ -294,11 +320,5 @@
   final Future<bool> hasIDeviceInstaller;
 
   @override
-  final Future<bool> hasCocoaPods;
-
-  @override
-  final Future<String> cocoaPodsVersionText;
-
-  @override
   final Future<String> macDevMode;
 }