Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/camera_avfoundation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.18+3

* Refactors implementations to reduce usage of OCMock in internal testing.

## 0.9.18+2

* Refactors implementations to reduce usage of OCMock in internal testing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
7FA99E592D22C75300582559 /* CameraExposureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FA99E582D22C75300582559 /* CameraExposureTests.m */; };
7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */; };
7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FCEDD322D43C2B900EA1CA8 /* MockCaptureDevice.m */; };
7FD582122D579650003B1200 /* MockWritableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD582112D579650003B1200 /* MockWritableData.m */; };
7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
Expand Down Expand Up @@ -102,6 +104,10 @@
7FCEDD322D43C2B900EA1CA8 /* MockCaptureDevice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCaptureDevice.m; sourceTree = "<group>"; };
7FCEDD332D43C2B900EA1CA8 /* MockDeviceOrientationProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockDeviceOrientationProvider.h; sourceTree = "<group>"; };
7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockDeviceOrientationProvider.m; sourceTree = "<group>"; };
7FD582112D579650003B1200 /* MockWritableData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockWritableData.m; sourceTree = "<group>"; };
7FD582132D57965A003B1200 /* MockWritableData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockWritableData.h; sourceTree = "<group>"; };
7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockCapturePhotoOutput.m; sourceTree = "<group>"; };
7FD582212D579ED9003B1200 /* MockCapturePhotoOutput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockCapturePhotoOutput.h; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -199,6 +205,8 @@
7F8FD2272D4BFA8D001AF2C1 /* MockGlobalEventApi.h */,
7FCEDD312D43C2B900EA1CA8 /* MockCaptureDevice.h */,
7FCEDD322D43C2B900EA1CA8 /* MockCaptureDevice.m */,
7FD582212D579ED9003B1200 /* MockCapturePhotoOutput.h */,
7FD5821F2D579ECC003B1200 /* MockCapturePhotoOutput.m */,
7FCEDD332D43C2B900EA1CA8 /* MockDeviceOrientationProvider.h */,
7FCEDD342D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m */,
7F29EB282D26A59000740257 /* MockCameraDeviceDiscoverer.m */,
Expand All @@ -207,6 +215,8 @@
7F29EB212D269ED500740257 /* MockEventChannel.m */,
7F29EB3E2D281C5800740257 /* MockCaptureSession.h */,
7F29EB402D281C7E00740257 /* MockCaptureSession.m */,
7FD582112D579650003B1200 /* MockWritableData.m */,
7FD582132D57965A003B1200 /* MockWritableData.h */,
);
path = Mocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -512,13 +522,15 @@
43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */,
E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */,
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */,
7FD582202D579ECC003B1200 /* MockCapturePhotoOutput.m in Sources */,
CEF6611A2B5E36A500D33FD4 /* CameraSessionPresetsTests.m in Sources */,
E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */,
7F29EB292D26A59000740257 /* MockCameraDeviceDiscoverer.m in Sources */,
788A065A27B0E02900533D74 /* StreamingTest.m in Sources */,
E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */,
7F29EB412D281C7E00740257 /* MockCaptureSession.m in Sources */,
E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */,
7FD582122D579650003B1200 /* MockWritableData.m in Sources */,
7FCEDD352D43C2B900EA1CA8 /* MockDeviceOrientationProvider.m in Sources */,
7FCEDD362D43C2B900EA1CA8 /* MockCaptureDevice.m in Sources */,
7F8FD22C2D4D07DD001AF2C1 /* MockFlutterTextureRegistry.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
#endif
@import AVFoundation;
@import XCTest;
#import <OCMock/OCMock.h>

#import "CameraTestUtils.h"
#import "MockCaptureDevice.h"
#import "MockCapturePhotoOutput.h"

/// Includes test cases related to photo capture operations for FLTCam class.
@interface FLTCamPhotoCaptureTests : XCTestCase
Expand All @@ -27,22 +29,19 @@ - (void)testCaptureToFile_mustReportErrorToResultIfSavePhotoDelegateCompletionsW
dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific,
(void *)FLTCaptureSessionQueueSpecific, NULL);
FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue);
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
id mockSettings = OCMClassMock([AVCapturePhotoSettings class]);
OCMStub([mockSettings photoSettings]).andReturn(settings);

NSError *error = [NSError errorWithDomain:@"test" code:0 userInfo:nil];

id mockOutput = OCMClassMock([AVCapturePhotoOutput class]);
OCMStub([mockOutput capturePhotoWithSettings:OCMOCK_ANY delegate:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init];
mockOutput.capturePhotoWithSettingsStub =
^(AVCapturePhotoSettings *settings, NSObject<AVCapturePhotoCaptureDelegate> *photoDelegate) {
FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)];
// Completion runs on IO queue.
dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL);
dispatch_async(ioQueue, ^{
delegate.completionHandler(nil, error);
});
});
};
cam.capturePhotoOutput = mockOutput;

// `FLTCam::captureToFile` runs on capture session queue.
Expand All @@ -67,22 +66,18 @@ - (void)testCaptureToFile_mustReportPathToResultIfSavePhotoDelegateCompletionsWi
(void *)FLTCaptureSessionQueueSpecific, NULL);
FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue);

AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
id mockSettings = OCMClassMock([AVCapturePhotoSettings class]);
OCMStub([mockSettings photoSettings]).andReturn(settings);

NSString *filePath = @"test";

id mockOutput = OCMClassMock([AVCapturePhotoOutput class]);
OCMStub([mockOutput capturePhotoWithSettings:OCMOCK_ANY delegate:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init];
mockOutput.capturePhotoWithSettingsStub =
^(AVCapturePhotoSettings *settings, NSObject<AVCapturePhotoCaptureDelegate> *photoDelegate) {
FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)];
// Completion runs on IO queue.
dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL);
dispatch_async(ioQueue, ^{
delegate.completionHandler(filePath, nil);
});
});
};
cam.capturePhotoOutput = mockOutput;

// `FLTCam::captureToFile` runs on capture session queue.
Expand All @@ -105,27 +100,19 @@ - (void)testCaptureToFile_mustReportFileExtensionWithHeifWhenHEVCIsAvailableAndF
FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue);
[cam setImageFileFormat:FCPPlatformImageFileFormatHeif];

AVCapturePhotoSettings *settings =
[AVCapturePhotoSettings photoSettingsWithFormat:@{AVVideoCodecKey : AVVideoCodecTypeHEVC}];

id mockSettings = OCMClassMock([AVCapturePhotoSettings class]);
OCMStub([mockSettings photoSettingsWithFormat:OCMOCK_ANY]).andReturn(settings);

id mockOutput = OCMClassMock([AVCapturePhotoOutput class]);
// Set availablePhotoCodecTypes to HEVC
NSArray *codecTypes = @[ AVVideoCodecTypeHEVC ];
OCMStub([mockOutput availablePhotoCodecTypes]).andReturn(codecTypes);

OCMStub([mockOutput capturePhotoWithSettings:OCMOCK_ANY delegate:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init];
mockOutput.availablePhotoCodecTypes = @[ AVVideoCodecTypeHEVC ];
mockOutput.capturePhotoWithSettingsStub =
^(AVCapturePhotoSettings *settings, NSObject<AVCapturePhotoCaptureDelegate> *photoDelegate) {
FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)];
// Completion runs on IO queue.
dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL);
dispatch_async(ioQueue, ^{
delegate.completionHandler(delegate.filePath, nil);
});
});
};
cam.capturePhotoOutput = mockOutput;

// `FLTCam::captureToFile` runs on capture session queue.
dispatch_async(captureSessionQueue, ^{
[cam captureToFileWithCompletion:^(NSString *filePath, FlutterError *error) {
Expand All @@ -146,22 +133,18 @@ - (void)testCaptureToFile_mustReportFileExtensionWithJpgWhenHEVCNotAvailableAndF
FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue);
[cam setImageFileFormat:FCPPlatformImageFileFormatHeif];

AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
id mockSettings = OCMClassMock([AVCapturePhotoSettings class]);
OCMStub([mockSettings photoSettings]).andReturn(settings);

id mockOutput = OCMClassMock([AVCapturePhotoOutput class]);

OCMStub([mockOutput capturePhotoWithSettings:OCMOCK_ANY delegate:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init];
mockOutput.capturePhotoWithSettingsStub =
^(AVCapturePhotoSettings *settings, NSObject<AVCapturePhotoCaptureDelegate> *photoDelegate) {
FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)];
// Completion runs on IO queue.
dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL);
dispatch_async(ioQueue, ^{
delegate.completionHandler(delegate.filePath, nil);
});
});
};
cam.capturePhotoOutput = mockOutput;

// `FLTCam::captureToFile` runs on capture session queue.
dispatch_async(captureSessionQueue, ^{
[cam captureToFileWithCompletion:^(NSString *filePath, FlutterError *error) {
Expand All @@ -176,12 +159,18 @@ - (void)testCaptureToFile_handlesTorchMode {
XCTestExpectation *pathExpectation =
[self expectationWithDescription:
@"Must send file path to result if save photo delegate completes with file path."];

id captureDeviceMock = OCMProtocolMock(@protocol(FLTCaptureDevice));
OCMStub([captureDeviceMock hasTorch]).andReturn(YES);
OCMStub([captureDeviceMock isTorchAvailable]).andReturn(YES);
OCMStub([captureDeviceMock torchMode]).andReturn(AVCaptureTorchModeAuto);
OCMExpect([captureDeviceMock setTorchMode:AVCaptureTorchModeOn]);
XCTestExpectation *setTorchExpectation =
[self expectationWithDescription:@"Should set torch mode to AVCaptureTorchModeOn."];

MockCaptureDevice *captureDeviceMock = [[MockCaptureDevice alloc] init];
captureDeviceMock.hasTorch = YES;
captureDeviceMock.isTorchAvailable = YES;
captureDeviceMock.torchMode = AVCaptureTorchModeAuto;
captureDeviceMock.setTorchModeStub = ^(AVCaptureTorchMode mode) {
if (mode == AVCaptureTorchModeOn) {
[setTorchExpectation fulfill];
}
};

dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL);
dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific,
Expand All @@ -194,22 +183,18 @@ - (void)testCaptureToFile_handlesTorchMode {
},
nil);

AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
id mockSettings = OCMClassMock([AVCapturePhotoSettings class]);
OCMStub([mockSettings photoSettings]).andReturn(settings);

NSString *filePath = @"test";

id mockOutput = OCMClassMock([AVCapturePhotoOutput class]);
OCMStub([mockOutput capturePhotoWithSettings:OCMOCK_ANY delegate:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
MockCapturePhotoOutput *mockOutput = [[MockCapturePhotoOutput alloc] init];
mockOutput.capturePhotoWithSettingsStub =
^(AVCapturePhotoSettings *settings, NSObject<AVCapturePhotoCaptureDelegate> *photoDelegate) {
FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)];
// Completion runs on IO queue.
dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL);
dispatch_async(ioQueue, ^{
delegate.completionHandler(filePath, nil);
});
});
};
cam.capturePhotoOutput = mockOutput;

// `FLTCam::captureToFile` runs on capture session queue.
Expand All @@ -223,6 +208,5 @@ - (void)testCaptureToFile_handlesTorchMode {
}];
});
[self waitForExpectationsWithTimeout:1 handler:nil];
OCMVerifyAll(captureDeviceMock);
}
@end
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
#endif
@import AVFoundation;
@import XCTest;
#import <OCMock/OCMock.h>

#import "MockWritableData.h"

@interface FLTSavePhotoDelegateTests : XCTestCase

Expand Down Expand Up @@ -55,16 +56,16 @@ - (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToWrite {
[completionExpectation fulfill];
}];

// Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
// `XCTRunnerIDESession::logDebugMessage:`) on a private queue.
id mockData = OCMPartialMock([NSData data]);
OCMStub([mockData writeToFile:OCMOCK_ANY
options:NSDataWritingAtomic
error:[OCMArg setTo:ioError]])
.andReturn(NO);
MockWritableData *mockWritableData = [[MockWritableData alloc] init];
mockWritableData.writeToFileStub =
^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) {
*error = ioError;
return NO;
};

[delegate handlePhotoCaptureResultWithError:nil
photoDataProvider:^NSData * {
return mockData;
photoDataProvider:^NSObject<FLTWritableData> * {
return mockWritableData;
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}
Expand All @@ -84,15 +85,15 @@ - (void)testHandlePhotoCaptureResult_mustCompleteWithFilePathIfSuccessToWrite {
[completionExpectation fulfill];
}];

// Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
// `XCTRunnerIDESession::logDebugMessage:`) on a private queue.
id mockData = OCMPartialMock([NSData data]);
OCMStub([mockData writeToFile:filePath options:NSDataWritingAtomic error:[OCMArg setTo:nil]])
.andReturn(YES);
MockWritableData *mockWritableData = [[MockWritableData alloc] init];
mockWritableData.writeToFileStub =
^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) {
return YES;
};

[delegate handlePhotoCaptureResultWithError:nil
photoDataProvider:^NSData * {
return mockData;
photoDataProvider:^NSObject<FLTWritableData> * {
return mockWritableData;
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}
Expand All @@ -109,16 +110,14 @@ - (void)testHandlePhotoCaptureResult_bothProvideDataAndSaveFileMustRunOnIOQueue
const char *ioQueueSpecific = "io_queue_specific";
dispatch_queue_set_specific(ioQueue, ioQueueSpecific, (void *)ioQueueSpecific, NULL);

// Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g.
// `XCTRunnerIDESession::logDebugMessage:`) on a private queue.
id mockData = OCMPartialMock([NSData data]);
OCMStub([mockData writeToFile:OCMOCK_ANY options:NSDataWritingAtomic error:[OCMArg setTo:nil]])
.andDo(^(NSInvocation *invocation) {
MockWritableData *mockWritableData = [[MockWritableData alloc] init];
mockWritableData.writeToFileStub =
^BOOL(NSString *path, NSDataWritingOptions options, NSError *__autoreleasing *error) {
if (dispatch_get_specific(ioQueueSpecific)) {
[writeFileQueueExpectation fulfill];
}
})
.andReturn(YES);
return YES;
};

NSString *filePath = @"test";
FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc]
Expand All @@ -129,11 +128,11 @@ - (void)testHandlePhotoCaptureResult_bothProvideDataAndSaveFileMustRunOnIOQueue
}];

[delegate handlePhotoCaptureResultWithError:nil
photoDataProvider:^NSData * {
photoDataProvider:^NSObject<FLTWritableData> * {
if (dispatch_get_specific(ioQueueSpecific)) {
[dataProviderQueueExpectation fulfill];
}
return mockData;
return mockWritableData;
}];

[self waitForExpectationsWithTimeout:1 handler:nil];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@import camera_avfoundation;
@import AVFoundation;

/// Mock implementation of `FLTCapturePhotoOutput` protocol which allows injecting a custom
/// implementation.
@interface MockCapturePhotoOutput : NSObject <FLTCapturePhotoOutput>

// Properties re-declared as read/write so a mocked value can be set during testing.
@property(nonatomic, strong) AVCapturePhotoOutput *photoOutput;
@property(nonatomic, strong) NSArray<AVVideoCodecType> *availablePhotoCodecTypes;
@property(nonatomic, assign) BOOL highResolutionCaptureEnabled;
@property(nonatomic, strong) NSArray<NSNumber *> *supportedFlashModes;

// Stub that is called when the corresponding public method is called.
@property(nonatomic, copy) void (^capturePhotoWithSettingsStub)
(AVCapturePhotoSettings *, NSObject<AVCapturePhotoCaptureDelegate> *);

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "MockCapturePhotoOutput.h"

@implementation MockCapturePhotoOutput
- (void)capturePhotoWithSettings:(AVCapturePhotoSettings *)settings
delegate:(id<AVCapturePhotoCaptureDelegate>)delegate {
if (self.capturePhotoWithSettingsStub) {
self.capturePhotoWithSettingsStub(settings, delegate);
}
}

- (nullable AVCaptureConnection *)connectionWithMediaType:(nonnull AVMediaType)mediaType {
return nil;
}

@end
Loading