[image_picker] Image picker fix camera device (#3898)

diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md
index 0e49912..33178de 100644
--- a/packages/image_picker/image_picker/CHANGELOG.md
+++ b/packages/image_picker/image_picker/CHANGELOG.md
@@ -1,9 +1,13 @@
+## 0.8.1+4
+
+* Fixes an issue where `preferredCameraDevice` option is not working for `getVideo` method.
+* Refactor unit tests that were device-only before.
+
 ## 0.8.1+3
 
 * Fix image picker causing a crash when the cache directory is deleted.
 
 ## 0.8.1+2
-
 * Update the example app to support the multi-image feature.
 
 ## 0.8.1+1
diff --git a/packages/image_picker/image_picker/example/ios/Podfile b/packages/image_picker/image_picker/example/ios/Podfile
index 75efae4..8979c25 100644
--- a/packages/image_picker/image_picker/example/ios/Podfile
+++ b/packages/image_picker/image_picker/example/ios/Podfile
@@ -31,7 +31,10 @@
   flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
 
   target 'RunnerTests' do
+    platform :ios, '9.0'
     inherit! :search_paths
+    # Pods for testing
+    pod 'OCMock', '~> 3.8.1'
   end
   target 'RunnerUITests' do
     inherit! :search_paths
diff --git a/packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.pbxproj b/packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.pbxproj
index 547c2be..fc1609f 100644
--- a/packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.pbxproj
@@ -877,7 +877,7 @@
 				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
 				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
 				CODE_SIGN_STYLE = Automatic;
-				DEVELOPMENT_TEAM = NHAKRD9N7D;
+				DEVELOPMENT_TEAM = "";
 				GCC_C_LANGUAGE_STANDARD = gnu11;
 				INFOPLIST_FILE = RunnerUITestiOS14/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.1;
diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m
index f667526..cc901f0 100644
--- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m
+++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m
@@ -6,6 +6,7 @@
 
 @import image_picker;
 @import XCTest;
+#import <OCMock/OCMock.h>
 
 @interface MockViewController : UIViewController
 @property(nonatomic, retain) UIViewController *mockPresented;
@@ -27,15 +28,33 @@
 @end
 
 @interface ImagePickerPluginTests : XCTestCase
+@property(readonly, nonatomic) id mockUIImagePicker;
+@property(readonly, nonatomic) id mockAVCaptureDevice;
 @end
 
 @implementation ImagePickerPluginTests
 
-#pragma mark - Test camera devices, no op on simulators
+- (void)setUp {
+  _mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
+  _mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]);
+}
+
 - (void)testPluginPickImageDeviceBack {
-  if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
-    return;
-  }
+  // UIImagePickerControllerSourceTypeCamera is supported
+  OCMStub(ClassMethod(
+              [_mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]))
+      .andReturn(YES);
+
+  // UIImagePickerControllerCameraDeviceRear is supported
+  OCMStub(ClassMethod(
+              [_mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear]))
+      .andReturn(YES);
+
+  // AVAuthorizationStatusAuthorized is supported
+  OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo])
+      .andReturn(AVAuthorizationStatusAuthorized);
+
+  // Run test
   FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
   FlutterMethodCall *call =
       [FlutterMethodCall methodCallWithMethodName:@"pickImage"
@@ -43,14 +62,27 @@
   [plugin handleMethodCall:call
                     result:^(id _Nullable r){
                     }];
+
   XCTAssertEqual([plugin getImagePickerController].cameraDevice,
                  UIImagePickerControllerCameraDeviceRear);
 }
 
 - (void)testPluginPickImageDeviceFront {
-  if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
-    return;
-  }
+  // UIImagePickerControllerSourceTypeCamera is supported
+  OCMStub(ClassMethod(
+              [_mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]))
+      .andReturn(YES);
+
+  // UIImagePickerControllerCameraDeviceFront is supported
+  OCMStub(ClassMethod([_mockUIImagePicker
+              isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]))
+      .andReturn(YES);
+
+  // AVAuthorizationStatusAuthorized is supported
+  OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo])
+      .andReturn(AVAuthorizationStatusAuthorized);
+
+  // Run test
   FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
   FlutterMethodCall *call =
       [FlutterMethodCall methodCallWithMethodName:@"pickImage"
@@ -58,14 +90,27 @@
   [plugin handleMethodCall:call
                     result:^(id _Nullable r){
                     }];
+
   XCTAssertEqual([plugin getImagePickerController].cameraDevice,
                  UIImagePickerControllerCameraDeviceFront);
 }
 
 - (void)testPluginPickVideoDeviceBack {
-  if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
-    return;
-  }
+  // UIImagePickerControllerSourceTypeCamera is supported
+  OCMStub(ClassMethod(
+              [_mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]))
+      .andReturn(YES);
+
+  // UIImagePickerControllerCameraDeviceRear is supported
+  OCMStub(ClassMethod(
+              [_mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear]))
+      .andReturn(YES);
+
+  // AVAuthorizationStatusAuthorized is supported
+  OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo])
+      .andReturn(AVAuthorizationStatusAuthorized);
+
+  // Run test
   FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
   FlutterMethodCall *call =
       [FlutterMethodCall methodCallWithMethodName:@"pickVideo"
@@ -73,10 +118,41 @@
   [plugin handleMethodCall:call
                     result:^(id _Nullable r){
                     }];
+
   XCTAssertEqual([plugin getImagePickerController].cameraDevice,
                  UIImagePickerControllerCameraDeviceRear);
 }
 
+- (void)testPluginPickVideoDeviceFront {
+  // UIImagePickerControllerSourceTypeCamera is supported
+  OCMStub(ClassMethod(
+              [_mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]))
+      .andReturn(YES);
+
+  // UIImagePickerControllerCameraDeviceFront is supported
+  OCMStub(ClassMethod([_mockUIImagePicker
+              isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]))
+      .andReturn(YES);
+
+  // AVAuthorizationStatusAuthorized is supported
+  OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo])
+      .andReturn(AVAuthorizationStatusAuthorized);
+
+  // Run test
+  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
+  FlutterMethodCall *call =
+      [FlutterMethodCall methodCallWithMethodName:@"pickVideo"
+                                        arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}];
+  [plugin handleMethodCall:call
+                    result:^(id _Nullable r){
+                    }];
+
+  XCTAssertEqual([plugin getImagePickerController].cameraDevice,
+                 UIImagePickerControllerCameraDeviceFront);
+}
+
+#pragma mark - Test camera devices, no op on simulators
+
 - (void)testPluginPickImageDeviceCancelClickMultipleTimes {
   if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
     return;
@@ -91,26 +167,13 @@
   plugin.result = ^(id result) {
 
   };
+  // To ensure the flow does not crash by multiple cancel call
   [plugin imagePickerControllerDidCancel:[plugin getImagePickerController]];
   [plugin imagePickerControllerDidCancel:[plugin getImagePickerController]];
 }
 
-- (void)testPluginPickVideoDeviceFront {
-  if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
-    return;
-  }
-  FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
-  FlutterMethodCall *call =
-      [FlutterMethodCall methodCallWithMethodName:@"pickVideo"
-                                        arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}];
-  [plugin handleMethodCall:call
-                    result:^(id _Nullable r){
-                    }];
-  XCTAssertEqual([plugin getImagePickerController].cameraDevice,
-                 UIImagePickerControllerCameraDeviceFront);
-}
-
 #pragma mark - Test video duration
+
 - (void)testPickingVideoWithDuration {
   FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
   FlutterMethodCall *call = [FlutterMethodCall
diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m
index 7c91606..4084ae6 100644
--- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m
+++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m
@@ -37,7 +37,6 @@
 
 @implementation FLTImagePickerPlugin {
   UIImagePickerController *_imagePickerController;
-  UIImagePickerControllerCameraDevice _device;
 }
 
 + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
@@ -70,6 +69,21 @@
   return topController;
 }
 
+/**
+ * Returns the UIImagePickerControllerCameraDevice to use given [arguments].
+ *
+ * If the cameraDevice value that is fetched from arguments is 1 then returns
+ * UIImagePickerControllerCameraDeviceFront. If the cameraDevice value that is fetched
+ * from arguments is 0 then returns UIImagePickerControllerCameraDeviceRear.
+ *
+ * @param arguments that should be used to get cameraDevice value.
+ */
+- (UIImagePickerControllerCameraDevice)getCameraDeviceFromArguments:(NSDictionary *)arguments {
+  NSInteger cameraDevice = [[arguments objectForKey:@"cameraDevice"] intValue];
+  return (cameraDevice == 1) ? UIImagePickerControllerCameraDeviceFront
+                             : UIImagePickerControllerCameraDeviceRear;
+}
+
 - (void)pickImageWithPHPicker:(int)maxImagesAllowed API_AVAILABLE(ios(14)) {
   PHPickerConfiguration *config =
       [[PHPickerConfiguration alloc] initWithPhotoLibrary:PHPhotoLibrary.sharedPhotoLibrary];
@@ -95,13 +109,9 @@
   self.maxImagesAllowed = 1;
 
   switch (imageSource) {
-    case SOURCE_CAMERA: {
-      NSInteger cameraDevice = [[_arguments objectForKey:@"cameraDevice"] intValue];
-      _device = (cameraDevice == 1) ? UIImagePickerControllerCameraDeviceFront
-                                    : UIImagePickerControllerCameraDeviceRear;
+    case SOURCE_CAMERA:
       [self checkCameraAuthorization];
       break;
-    }
     case SOURCE_GALLERY:
       [self checkPhotoAuthorization];
       break;
@@ -188,11 +198,12 @@
       return;
     }
   }
+  UIImagePickerControllerCameraDevice device = [self getCameraDeviceFromArguments:_arguments];
   // Camera is not available on simulators
   if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] &&
-      [UIImagePickerController isCameraDeviceAvailable:_device]) {
+      [UIImagePickerController isCameraDeviceAvailable:device]) {
     _imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
-    _imagePickerController.cameraDevice = _device;
+    _imagePickerController.cameraDevice = device;
     [[self viewControllerWithWindow:nil] presentViewController:_imagePickerController
                                                       animated:YES
                                                     completion:nil];
@@ -406,8 +417,8 @@
  * The difference with initWithCapacity is that initWithCapacity still gives an empty array making
  * it impossible to add objects on an index larger than the size.
  *
- * @param @size The length of the required array
- * @return @NSMutableArray An array of a specified size
+ * @param size The length of the required array
+ * @return NSMutableArray An array of a specified size
  */
 - (NSMutableArray *)createNSMutableArrayWithSize:(NSUInteger)size {
   NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:size];
@@ -528,14 +539,14 @@
  * Applies NSMutableArray on the FLutterResult.
  *
  * NSString must be returned by FlutterResult if the single image
- * mode is active. It is checked by @c maxImagesAllowed and
- * returns the first object of the @c pathlist.
+ * mode is active. It is checked by maxImagesAllowed and
+ * returns the first object of the pathlist.
  *
  * NSMutableArray must be returned by FlutterResult if the multi-image
- * mode is active. After the @c pathlist count is checked then it returns
- * the @c pathlist.
+ * mode is active. After the pathlist count is checked then it returns
+ * the pathlist.
  *
- * @param @pathList that should be applied to FlutterResult.
+ * @param pathList that should be applied to FlutterResult.
  */
 - (void)handleSavedPathList:(NSArray *)pathList {
   if (!self.result) {
diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml
index bcda757..c9866db 100755
--- a/packages/image_picker/image_picker/pubspec.yaml
+++ b/packages/image_picker/image_picker/pubspec.yaml
@@ -3,7 +3,7 @@
   library, and taking new pictures with the camera.
 repository: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 0.8.1+3
+version: 0.8.1+4
 
 environment:
   sdk: ">=2.12.0 <3.0.0"