Skip to content

Commit

Permalink
[camera] Remove OCMock from permission tests (flutter#8350)
Browse files Browse the repository at this point in the history
Smaller part extracted from flutter#8342

- Removes OCMock from `CameraPermissionTests`
- Wraps permission methods into a new interface `FLTCameraPermissionManager`
- Introduces new protocol which wraps framework methods and can be mocked directly `FLTPermissionService`
  • Loading branch information
mchudy authored Dec 30, 2024
1 parent 29b2606 commit 3976c1b
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 185 deletions.
3 changes: 2 additions & 1 deletion packages/camera/camera_avfoundation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT
## 0.9.17+6

* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.
* Removes OCMock usage from permission tests

## 0.9.17+5

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,58 @@
#endif
@import AVFoundation;
@import XCTest;
#import <OCMock/OCMock.h>

#import "CameraTestUtils.h"

@interface CameraPermissionTests : XCTestCase
@interface MockPermissionService : NSObject <FLTPermissionServicing>
@property(nonatomic, copy) AVAuthorizationStatus (^authorizationStatusStub)(AVMediaType mediaType);
@property(nonatomic, copy) void (^requestAccessStub)(AVMediaType mediaType, void (^handler)(BOOL));
@end

@implementation MockPermissionService
- (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType {
return self.authorizationStatusStub ? self.authorizationStatusStub(mediaType)
: AVAuthorizationStatusNotDetermined;
}

- (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL))handler {
if (self.requestAccessStub) {
self.requestAccessStub(mediaType, handler);
}
}
@end

@implementation CameraPermissionTests
@interface FLTCameraPermissionManagerTests : XCTestCase
@property(nonatomic, strong) FLTCameraPermissionManager *permissionManager;
@property(nonatomic, strong) MockPermissionService *mockService;
@end

@implementation FLTCameraPermissionManagerTests

- (void)setUp {
[super setUp];
self.mockService = [[MockPermissionService alloc] init];
self.permissionManager =
[[FLTCameraPermissionManager alloc] initWithPermissionService:self.mockService];
}

#pragma mark - camera permissions

- (void)testRequestCameraPermission_completeWithoutErrorIfPrevoiuslyAuthorized {
- (void)testRequestCameraPermission_completeWithoutErrorIfPreviouslyAuthorized {
XCTestExpectation *expectation =
[self expectationWithDescription:
@"Must copmlete without error if camera access was previously authorized."];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo])
.andReturn(AVAuthorizationStatusAuthorized);
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeVideo);
return AVAuthorizationStatusAuthorized;
};

FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) {
[self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) {
if (error == nil) {
[expectation fulfill];
}
});
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testRequestCameraPermission_completeWithErrorIfPreviouslyDenied {
Expand All @@ -45,14 +72,16 @@ - (void)testRequestCameraPermission_completeWithErrorIfPreviouslyDenied {
@"Settings to enable camera access."
details:nil];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo])
.andReturn(AVAuthorizationStatusDenied);
FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) {
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeVideo);
return AVAuthorizationStatusDenied;
};

[self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) {
if ([error isEqual:expectedError]) {
[expectation fulfill];
}
});
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

Expand All @@ -63,37 +92,39 @@ - (void)testRequestCameraPermission_completeWithErrorIfRestricted {
message:@"Camera access is restricted. "
details:nil];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo])
.andReturn(AVAuthorizationStatusRestricted);
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeVideo);
return AVAuthorizationStatusRestricted;
};

FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) {
[self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) {
if ([error isEqual:expectedError]) {
[expectation fulfill];
}
});
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testRequestCameraPermission_completeWithoutErrorIfUserGrantAccess {
XCTestExpectation *grantedExpectation = [self
expectationWithDescription:@"Must complete without error if user choose to grant access"];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo])
.andReturn(AVAuthorizationStatusNotDetermined);
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeVideo);
return AVAuthorizationStatusNotDetermined;
};

// Mimic user choosing "allow" in permission dialog.
OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeVideo
completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) {
block(YES);
return YES;
}]]);
self.mockService.requestAccessStub = ^(AVMediaType mediaType, void (^handler)(BOOL)) {
XCTAssertEqualObjects(mediaType, AVMediaTypeVideo);
handler(YES);
};

FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) {
[self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) {
if (error == nil) {
[grantedExpectation fulfill];
}
});
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

Expand All @@ -105,21 +136,22 @@ - (void)testRequestCameraPermission_completeWithErrorIfUserDenyAccess {
message:@"User denied the camera access request."
details:nil];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo])
.andReturn(AVAuthorizationStatusNotDetermined);
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeVideo);
return AVAuthorizationStatusNotDetermined;
};

// Mimic user choosing "deny" in permission dialog.
OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeVideo
completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) {
block(NO);
return YES;
}]]);
FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) {
self.mockService.requestAccessStub = ^(AVMediaType mediaType, void (^handler)(BOOL)) {
XCTAssertEqualObjects(mediaType, AVMediaTypeVideo);
handler(NO);
};

[self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) {
if ([error isEqual:expectedError]) {
[expectation fulfill];
}
});
}];

[self waitForExpectationsWithTimeout:1 handler:nil];
}
Expand All @@ -131,17 +163,19 @@ - (void)testRequestAudioPermission_completeWithoutErrorIfPrevoiuslyAuthorized {
[self expectationWithDescription:
@"Must copmlete without error if audio access was previously authorized."];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio])
.andReturn(AVAuthorizationStatusAuthorized);
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeAudio);
return AVAuthorizationStatusAuthorized;
};

FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
[self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) {
if (error == nil) {
[expectation fulfill];
}
});
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testRequestAudioPermission_completeWithErrorIfPreviouslyDenied {
XCTestExpectation *expectation =
[self expectationWithDescription:
Expand All @@ -152,14 +186,16 @@ - (void)testRequestAudioPermission_completeWithErrorIfPreviouslyDenied {
@"Settings to enable audio access."
details:nil];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio])
.andReturn(AVAuthorizationStatusDenied);
FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeAudio);
return AVAuthorizationStatusDenied;
};

[self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) {
if ([error isEqual:expectedError]) {
[expectation fulfill];
}
});
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

Expand All @@ -170,37 +206,39 @@ - (void)testRequestAudioPermission_completeWithErrorIfRestricted {
message:@"Audio access is restricted. "
details:nil];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio])
.andReturn(AVAuthorizationStatusRestricted);
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeAudio);
return AVAuthorizationStatusRestricted;
};

FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
[self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) {
if ([error isEqual:expectedError]) {
[expectation fulfill];
}
});
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testRequestAudioPermission_completeWithoutErrorIfUserGrantAccess {
XCTestExpectation *grantedExpectation = [self
expectationWithDescription:@"Must complete without error if user choose to grant access"];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio])
.andReturn(AVAuthorizationStatusNotDetermined);
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeAudio);
return AVAuthorizationStatusNotDetermined;
};

// Mimic user choosing "allow" in permission dialog.
OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeAudio
completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) {
block(YES);
return YES;
}]]);
self.mockService.requestAccessStub = ^(AVMediaType mediaType, void (^handler)(BOOL)) {
XCTAssertEqualObjects(mediaType, AVMediaTypeAudio);
handler(YES);
};

FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
[self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) {
if (error == nil) {
[grantedExpectation fulfill];
}
});
}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

Expand All @@ -211,22 +249,22 @@ - (void)testRequestAudioPermission_completeWithErrorIfUserDenyAccess {
message:@"User denied the audio access request."
details:nil];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio])
.andReturn(AVAuthorizationStatusNotDetermined);
self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) {
XCTAssertEqualObjects(mediaType, AVMediaTypeAudio);
return AVAuthorizationStatusNotDetermined;
};

// Mimic user choosing "deny" in permission dialog.
OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeAudio
completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) {
block(NO);
return YES;
}]]);
FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
self.mockService.requestAccessStub = ^(AVMediaType mediaType, void (^handler)(BOOL)) {
XCTAssertEqualObjects(mediaType, AVMediaTypeAudio);
handler(NO);
};

[self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) {
if ([error isEqual:expectedError]) {
[expectation fulfill];
}
});

}];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

Expand Down
Loading

0 comments on commit 3976c1b

Please sign in to comment.