I am using CloudKit in my application and am trying to mock CKContainer to test my Managers. Here is what i tried:
func testAccountStatus() {
class MockCloudContainer: CKContainer {
override func accountStatusWithCompletionHandler(completionHandler: ((CKAccountStatus, NSError!) -> Void)!)
{
completionHandler(CKAccountStatus.NoAccount, NSError())
}
}
let loginManager = LoginManager.sharedInstance
let expectation = expectationWithDescription("iCloudStatus")
var isTestFinished = false
loginManager.iCloudStatusWithCompletionHandler { (status, error) -> Void in
if (isTestFinished) {
return
}
expectation.fulfill()
XCTAssertTrue(status.isEqualToString("NoAccount"), "Status is Available")
}
waitForExpectationsWithTimeout(5, { error in
isTestFinished = true
XCTAssertNil(error, "Error")
})
But i am getting error while compiling the code
:0: error: cannot override 'init' which has been marked unavailable
What is best way I am using mock object to test my LoginManager class ?
Thanks
Currently there is no way to mock the CKContainer. The only solution would be to create a thin data access layer between your app and the CloudKit code and then mock that layer. But still you would not be able to unit test that thin layer itself.
I have an idea for this problem. But I'm not good at swift so I provide my code in Objective-C
Firstly , try create CKContainerMock in your testClass like this:
#interface CKContainerMock : CKContainer
#property (nonatomic, copy) void (^mCompletionHandlerMock)(CKAccountStatus accountStatus,NSError *error);
- (void)accountStatusWithCompletionHandler:(void (^)(CKAccountStatus accountStatus, NSError *error))completionHandler
#end
#implementation CKContainerMock
- (void)accountStatusWithCompletionHandler:(void (^)(CKAccountStatus accountStatus, NSError *error))completionHandler {
self.mCompletionHandlerMock = completionHandler;
}
#end
In your testFunction
Example:
Success case:
-(void) testCKContainerResultSucces {
LoginManager loginManager = [LoginManager sharedInstance];
CKContainerMock *ckContainerMock = [[CKContainerMock alloc] init];
[loginManager setCKContainer:ckContainerMock]; // Change your ckContainer object to mockObject.
[loginManager iCloudStatusWithCompletionHandler:^(CKAccountStatus accountStatus,NSError *error) {
XCAssert(accountStatus == CKAccountStatusAvailable);
}];
loginManager.ckContainer.mCompletionHandlerMock(CKAccountStatusAvailable,nil);
}
Error case:
-(void) testCKContainerResultError {
LoginManager loginManager = [LoginManager sharedInstance];
CKContainerMock *ckContainerMock = [[CKContainerMock alloc] init];
[loginManager setCKContainer:ckContainerMock]; //Change your ckContainer object to mockObject.
[loginManager iCloudStatusWithCompletionHandler:^(CKAccountStatus accountStatus,NSError *error) {
XCAssertNotNil(error);
}];
loginManager.ckContainer.mCompletionHandlerMock(nil,[NSError errorWithDomain:NSPOSIXErrorDomain code:22 userInfo:nil]);
}
Greate mock example for you
Hope this help.
Related
I am trying to integrate Hyperpay payment into React Native project and I have problems with objective-c, I followed an article and found many issues and with searching, I solve them, but still two issues I can't solve because I am not familiar with objective-c
Issue 1,
No known class method for selector 'presentCheckoutForSubmittingTransactionCompletionHandler:cancelHandler:'
Issue 2,
No known class method for selector 'dismissCheckoutAnimated:completion:'
I am sorry if my code is long but I don't to miss something
// RCTCalendarModule.m
#import "HyperPay.h"
#import "UIKit/UIKit.h"
#import <OPPWAMobile/OPPWAMobile.h>
#implementation HyperPay{
RCTResponseSenderBlock onDoneClick;
RCTResponseSenderBlock onCancelClick;
UIViewController *rootViewController;
NSString *isRedirect;
OPPPaymentProvider *provider;
}
// To export a module named RCTCalendarModule
RCT_EXPORT_METHOD(openHyperPay:(NSDictionary *)indic createDialog:(RCTResponseSenderBlock)doneCallback createDialog:(RCTResponseSenderBlock)cancelCallback) {
onDoneClick = doneCallback;
onCancelClick = cancelCallback;
NSArray *events = #[];
if ([indic[#"is_sandbox"] isEqualToString:#"1"]) {
provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeTest];
} else {
provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeLive];
}
OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
// Set available payment brands for your shop
checkoutSettings.paymentBrands = #[#"VISA", #"MASTER"];
// Set shopper result URL
checkoutSettings.shopperResultURL = #"com.simicart.enterprise.payments://result";
OPPCheckoutProvider *checkoutProvider = [OPPCheckoutProvider checkoutProviderWithPaymentProvider:provider checkoutID:indic[#"checkoutId"]
settings:checkoutSettings];
dispatch_async(dispatch_get_main_queue(), ^{
[OPPCheckoutProvider presentCheckoutForSubmittingTransactionCompletionHandler:^(OPPTransaction * _Nullable transaction, NSError * _Nullable error) {
if (error) {
// Executed in case of failure of the transaction for any reason
if (isRedirect && ![isRedirect isEqualToString:#"1"]) {
onCancelClick(#[#"cancel", events]);
}
} else if (transaction.type == OPPTransactionTypeSynchronous) {
// Send request to your server to obtain the status of the synchronous transaction
// You can use transaction.resourcePath or just checkout id to do it
NSDictionary *responeDic = #{#"resourcePath" : transaction.resourcePath};
onDoneClick(#[responeDic, events]);
NSLog(#"%#", transaction.resourcePath);
} else {
// The SDK opens transaction.redirectUrl in a browser
// See 'Asynchronous Payments' guide for more details
}
} cancelHandler:^{
onCancelClick(#[#"cancel", events]);
// Executed if the shopper closes the payment page prematurely
}];
});
}
- (instancetype)init{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(getStatusOder:) name:#"getStatusOrder" object:nil];
}
return self;
}
- (void)getStatusOder:(NSNotification*)noti{
[OPPCheckoutProvider dismissCheckoutAnimated:YES completion:^{
isRedirect = #"1";
NSURL *url = noti.object;
NSString *urlString = [url absoluteString];
NSLog(#"%#", urlString);
if (![urlString isEqualToString:#"com.simicart.enterprise.payments://result"]) {
NSArray *events = #[];
NSDictionary *responeDic = #{#"url" : urlString};
onDoneClick(#[responeDic, events]);
}
}];
}
#end
Temboo provides Objective-C code for their Choreos (API requests). I used Temboo instructions and able to get the code working inside viewVontroller.m:
#import "ViewController.h"
#import "TMBUtilities.h"
#import "TMBChoreography.h"
#import "TMBTembooSession.h"
#interface Post : NSObject <TMBChoreographyDelegate>
-(void)runPostChoreo;
-(void)choreographyDidFailWithError:(NSError*)error;
-(void)choreographyDidFinishExecuting:
(TMBUtilities_HTTP_Post_ResultSet*)result;
#end
#implementation Post
-(void)runPostChoreo {
TMBTembooSession *session = [[TMBTembooSession alloc] initWithAccount:#"xxxxx" appKeyName:#"xxxxx" andAppKeyValue:#"xxxxx"];
TMBUtilities_HTTP_Post *postChoreo = [[TMBUtilities_HTTP_Post alloc] initWithSession:session];
TMBUtilities_HTTP_Post_Inputs *postInputs = [postChoreo newInputSet];
[postInputs setUsername:#"xxxxx"];
[postInputs setURL:#"https://anywebsite.com/notes"];
[postInputs setPassword:#"xxxxx"];
[postInputs setRequestParameters:#"{\"utf8\":\"xxxxx\",\"xxxxx\":\"Post This Info\"}"];
[postChoreo executeWithInputs:postInputs delegate:self];
}
-(void)choreographyDidFailWithError:(NSError*)error {
NSLog(#"Error - %#", error);
}
-(void)choreographyDidFinishExecuting:(TMBUtilities_HTTP_Post_ResultSet*)result {
NSLog(#"%#", [result getHTTPLog]);
NSLog(#"%#", [result getResponseStatusCode]);
NSLog(#"%#", [result getResponse]);
}
#end
#implementation ViewController
- (IBAction)buttonPost:(id)sender {
dispatch_async(dispatch_get_main_queue(), ^{
Post *test = [[Post alloc] init];
[test runPostChoreo];
});
}
for Swift conversion, I have following bridging table
#import "TMBUtilities.h"
#import "TMBChoreography.h"
#import "TMBTembooSession.h"
Then I added converted Temboo code at the top of the ViewController.Swift. I am getting following error at the top line "Type 'Post' does not conform to protocol 'TMBChoreographyDelegate'". I will appreciate any hint on this. Thanks.
class Post: NSObject, TMBChoreographyDelegate {
func runPostChoreo() {}
func choreographyDidFailWithError(error:NSError) {}
func choreographyDidFinishExecuting(result: TMBUtilities_HTTP_Post_ResultSet) {}
}
class Post{
func runPostChoreo() {
let session: TMBTembooSession = TMBTembooSession(account: "xxxxx", appKeyName: "xxxxx", andAppKeyValue: "xxxxx")
let postChoreo: TMBUtilities_HTTP_Post = TMBUtilities_HTTP_Post(session: session)
let postInputs: TMBUtilities_HTTP_Post_Inputs = postChoreo.newInputSet()
postInputs.setUsername("xxxxx")
postInputs.setURL("https://anywebsite.com/notes")
postInputs.setPassword("xxxxx")
postInputs.setRequestParameters("{\"utf8\":\"xxxx\",\"xxxxx\":\"Post This Info\"}")
postChoreo.executeWithInputs(postInputs, delegate: self)
}
func choreographyDidFailWithError(error: NSError) {
NSLog("Error - %#", error)
}
func choreographyDidFinishExecuting(result: TMBUtilities_HTTP_Post_ResultSet) {
NSLog("%#", result.getHTTPLog())
NSLog("%#", result.getResponseStatusCode())
NSLog("%#", result.getResponse())
}
}
class ViewController: UIViewController {
#IBAction func myButton(sender: AnyObject) {
dispatch_async(dispatch_get_main_queue(), {() -> Void in
var test: Post = Post()
test.runPostChoreo()
myRunLoop.run()
})
I'm not sure I understand why you declared class Post 2 times in your Swift code. I assume it's because the Objective-C code has an interface and an implementation. Swift does not do things that way.
Change the code to only have class Post once as follows:
TMBChoreographyDelegate already inherits NSObject so there is no need to do it again.
#protocol TMBChoreographyDelegate <NSObject>
Proposed code rewrite:
class Post: TMBChoreographyDelegate{
// This function is required for TMBChoreographyDelegate
func runPostChoreo() {
let session: TMBTembooSession = TMBTembooSession(account: "xxxxx", appKeyName: "xxxxx", andAppKeyValue: "xxxxx")
let postChoreo: TMBUtilities_HTTP_Post = TMBUtilities_HTTP_Post(session: session)
let postInputs: TMBUtilities_HTTP_Post_Inputs = postChoreo.newInputSet()
postInputs.setUsername("xxxxx")
postInputs.setURL("https://anywebsite.com/notes")
postInputs.setPassword("xxxxx")
postInputs.setRequestParameters("{\"utf8\":\"xxxx\",\"xxxxx\":\"Post This Info\"}")
postChoreo.executeWithInputs(postInputs, delegate: self)
}
// This function is required for TMBChoreographyDelegate
func choreographyDidFailWithError(error: NSError) {
print("Error - \(error)")
}
func choreographyDidFinishExecuting(result: TMBUtilities_HTTP_Post_ResultSet) {
print(result.getHTTPLog())
print(result.getResponseStatusCode())
print(result.getResponse())
}
}
I am trying to bring obj-c code to swift (facebook ios sdk), but autocomplete(intellisense) does not work in handler and I get an error (marked in code) : Set NSObject does not have a member named 'containsObject'
#IBAction func loginWithFacebook(sender: AnyObject) {
/*
FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
[login logInWithReadPermissions:#[#"email"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
if (error) {
// Process error
} else if (result.isCancelled) {
// Handle cancellations
} else {
// If you ask for multiple permissions at once, you
// should check if specific permissions missing
if ([result.grantedPermissions containsObject:#"email"]) {
// Do work
}
}
}];
*/
let fbLoginManager = FBSDKLoginManager()
fbLoginManager.logInWithReadPermissions(["email"], handler: {
result, error in
if ((error) != nil){
}
else if (result.isCancelled){
} else {
if(result.grantedPermissions.containsObject("email")){ //<-- error here
}
}
})
}
Because Swift 1.2 automatically casts all NSSet objects (the ones that coming from external libs/sdks/frameworks etc...) to Set structure you need to call contains instead of containsObject for such things (doc).
I'm implementing v4.1 of the SDK for iOS and when I try to call for publishPermissions:, I get no callback.
For some reason everything works perfectly when I run logInWithReadPermissions:, but when I run logInWithPublishPermissions: it never hits my response handler. Nothing happens.
To test things out, I reset my loginManager before running logInWithPublishPermissions:, and to my surprise it worked then (aka NSLog(#"RESULT") is called).
Am I missing something about how the loginManager works? Shouldn't I be able to use it without resetting it?
// FacebookController.m
#implementation FacebookController
FBSDKLoginManager *loginManager;
static FacebookController *_shared = nil;
- (id)init {
self = [super init];
if (self != nil) {
userData = [[NSMutableDictionary alloc] init];
loginManager = [[FBSDKLoginManager alloc] init];
}
return self;
}
+ (id)getInstance {
if (!_shared) {
_shared = [[self alloc] init];
}
return _shared;
}
- (bool)hasPublishPermissions {
FBSDKAccessToken *accessToken = [FBSDKAccessToken currentAccessToken];
if(accessToken != NULL){
NSSet *permissions = [accessToken permissions];
if([permissions containsObject:#"publish_actions"]){
return TRUE;
}
}
return FALSE;
}
- (void)requestPublishPermissionsWithDelegate:(id)aDelegate {
if(![self hasPublishPermissions]){
// FOR SOME REASON IT WORKS IF I RESET LOGIN MANAGER AS FOLLOWS
// loginManager = [[FBSDKLoginManager alloc] init];
[loginManager logInWithPublishPermissions:#[#"publish_actions"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
NSLog(#"RESULT: %#", result);
}];
}
}
- (void)connectToFacebookWithDelegate:(id)aDelegate {
FBSDKAccessToken *accessToken = [FBSDKAccessToken currentAccessToken];
if(accessToken != nil){
[aDelegate performSelector:#selector(facebookSignedIn)];
} else {
[loginManager logInWithReadPermissions:#[#"email"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
if (error) {
// Process error
NSLog(#"ERROR");
} else if (result.isCancelled) {
// Handle cancellations
NSLog(#"CANCELLED");
} else {
NSLog(#"SUCCESS");
[aDelegate performSelector:#selector(facebookSignedIn)];
}
}];
}
}
#end
Edit #1:
Including videos of it working and not working for the given scenarios:
Not working (loginManager reinitialization commented out):
https://dl.dropboxusercontent.com/u/14277258/not-working.mov
Working (loginManager reinitialized):
https://dl.dropboxusercontent.com/u/14277258/working.mov
Your video stack trace indicates you're calling the request for publish permissions inside the handler for your initial login. This should be avoided:
You're causing another login after the user has already granted you some permissions - it's not very good for the user to have to see another login dialog immediately after completing one.
You're asking for publish permissions when you don't need it - this may violate Facebook developer policies and again is not the best user experience. Instead you should asking for publish only when you need it (i.e., at the time of sharing).
If you really insist, you can dispatch your second login call asynchronously so that the first request finishes entirely but I wouldn't recommend it. We can probably update the SDK to detect this and log though so it's not as confusing.
I have been playing with icloud in the ios 8 beta, and the CloudKitAtlasAnIntroductiontoCloudKit sample project has been very helpful.
https://developer.apple.com/library/prerelease/ios/samplecode/CloudAtlas/Introduction/Intro.html
But I wanted to use the CKDiscoverAllContactsOperation class and I cannot find any sample code for it anywhere at all and the online documentation is not very helpful.
https://developer.apple.com/library/prerelease/ios/documentation/CloudKit/Reference/CKDiscoverAllContactsOperation_class/index.html
If anyone has managed to successfully use CKDiscoverAllContactsOperation could you please help point me in the right direction or show a working example of how it should be called?
I have tried this to see if I could even get an response from iCloud but nothing:
- (void)queryForRecordsOtherUsersInAddressBookcompletionHandler:(void (^)(NSArray *records))completionHandler {
CKDiscoverAllContactsOperation *discoverAllContactsOperation= [[CKDiscoverAllContactsOperation alloc] init];
[discoverAllContactsOperation setContainer:_container];
NSMutableArray *results = [[NSMutableArray alloc] init];
discoverAllContactsOperation.discoverAllContactsCompletionBlock = ^(NSArray *userInfos, NSError *operationError) {
[results addObjectsFromArray:userInfos];
};
discoverAllContactsOperation.discoverAllContactsCompletionBlock=^(NSArray *userInfos, NSError *operationError){
if (operationError) {
// In your app, handle this error with such perfection that your users will never realize an error occurred.
NSLog(#"An error occured in %#: %#", NSStringFromSelector(_cmd), operationError);
abort();
} else {
dispatch_async(dispatch_get_main_queue(), ^(void){
completionHandler(results);
});
}
};
}
and calling with this...
[self.cloudManager queryForRecordsOtherUsersInAddressBookcompletionHandler:^(NSArray *records ) {
if (records.count==0){
NSLog(#"Login name not found");
return;
}
//self.results= records;
//_loggedInRecord = self.results[0];
//NSLog(#"%#,%#",_loggedInRecord[#"lastName"],_loggedInRecord[#"firstName"]);
// [self performSegueWithIdentifier:#"loggedInSegue" sender:self ];
}];
I know the code shouldn't really do anything. Again I was just looking for a response from iCloud.
Here is what I am using. self.container is a CKContainer set with [CKContainer defaultContainer] in the init.
-(void)queryForAllUsers: (void (^)(NSArray *records))completionHandler {
CKDiscoverAllContactsOperation *op = [[CKDiscoverAllContactsOperation alloc] init];
[op setUsesBackgroundSession:YES];
op.queuePriority = NSOperationQueuePriorityNormal;
[op setDiscoverAllContactsCompletionBlock:^(NSArray *userInfos, NSError *error) {
if (error) {
NSLog(#"An error occured in %#: %#", NSStringFromSelector(_cmd), error);
//abort();
} else {
// NSLog(#"Number of records in userInfos is: %ld", (unsigned long)[userInfos count]);
dispatch_async(dispatch_get_main_queue(), ^(void){
completionHandler(userInfos);
});
}
}];
[self.container addOperation:op];
}
Before you can use the CKDiscoverAllContactsOperation operation, you first need to request for permission.
Pls use the method requestApplicationPermission:completion:
func discoverAllContacts() {
let container = CKContainer.defaultContainer()
//Request for user permission
container.requestApplicationPermission([.UserDiscoverability]) { [weak self] status, error in
switch status {
case .Granted where error == nil:
let operation = self?.discoverAllContactsOperation { usersInfo in
//do something here
}
if let operationExists = operation {
//Assuming there is a NSOperationQueue property called operationQueue
self?.operationQueue.addOperation(operationExists)
}
default:
break
}
}
}
func discoverAllContactsOperation(completionHandler: ([CKDiscoveredUserInfo]?) -> ()) -> NSOperation {
let operation = CKDiscoverAllContactsOperation()
operation.discoverAllContactsCompletionBlock = { usersInfo, error in
if error == nil {
print("Discoverd all contacts = \(usersInfo)")
completionHandler(usersInfo)
}
else {
print("Discoverd all contacts error = \(error)")
completionHandler(nil)
}
}
return operation
}