how to use FBTestSession with Setup call of unit test - integration-testing

I'm trying to set up my unit test to use the latest facebook SDK and am running into issues...
Here is the code i have:
[FBSession setDefaultAppID: #"323351877676429"];
FBTestSession *session = [FBTestSession sessionWithSharedUserWithPermissions: [NSArray array]];
STAssertNotNil(session, #"could not create test session");
[FBTestSession openActiveSessionWithReadPermissions:#[]
allowLoginUI:NO
completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
NSLog(#"completed");
[FBSession setActiveSession: session];
STAssertNil(error, error.description);
STAssertNotNil(FBSession.activeSession, #"FBSession missing");
STAssertNotNil(FBSession.activeSession.accessTokenData, #"FBSession missing");
STAssertNotNil(FBSession.activeSession.accessTokenData.accessToken, #"facebook token is nil");
STAssertNotNil(session, #"FBSession missing");
STAssertNotNil(session.accessTokenData, #"FBSession missing");
STAssertNotNil(session.accessTokenData.accessToken, #"facebook token is nil");
userModel *userM = [[userModel alloc] init];
[userM awakeFromNib];
} ];
This works well except that as it runs asynchronously, when my test are run the session is not ready and thus i have session.accessTokenData == nil., which make my other test fail.
If i add the below code to my set up method after the openSession call, then the setup method never returns.
while (!FBSession.activeSession.accessTokenData)
[NSThread sleepForTimeInterval:.2];
Is there an exemple of proper use of FBTestSession somewhere? Any clue as to how to proceed?
Thanks, olivier

You are close, you just need to wait until the blocks are complete. Here is a working example (slightly shortened/modified) that you can use for reference.
- (void)testFacebookLogin {
__block bool blockFinished = NO;
FBTestSession *fbSession = [FBTestSession sessionWithSharedUserWithPermissions:#[#"email"]];
[fbSession openWithCompletionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
NSLog(#"session %#, status %d, error %#", session, status, error);
[FBSession setActiveSession:session];
FBRequest *me = [FBRequest requestForMe];
NSLog(#"me request %#", me);
[me startWithCompletionHandler: ^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *my,
NSError *error) {
STAssertNotNil(my.id, #"id shouldn't be nil");
blockFinished = YES;
}];
}];
// Run loop
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:10];
while (blockFinished == NO && [loopUntil timeIntervalSinceNow] > 0) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:loopUntil];
}
}

Related

How to update Core Data and maintain user-saved data?

I have an app on the App Store that uses Core Data and I want to make an update. I use a preloaded sqlite file to create the persistentStore. There are two entities in the model, one of which holds user-saved data.
My issue: I want to update the existing Core Data with a new preloaded sqlite, but preserve the user-saved data. Is there a way to merge the new data of the preloaded file with the user-saved data?
The model remains the same; I haven't added any new entities or attributes, so I don't think a migration is appropriate (I could be wrong).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CreatureData.sqlite"];
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"CreatureData" ofType:#"sqlite"]];
NSLog(#"JUST LOADED COREDATA SQLITE");
NSError* err = nil;
if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
NSLog(#"Oops, couldn't copy preloaded data");
}
} else {
NSLog(#"Core Data SQLITE already exists");
}
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
//abort();
}
return _persistentStoreCoordinator;
}
EDIT:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CreatureData.sqlite"];
NSURL *secondStoreURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"CreatureData" ofType:#"sqlite"]];
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"CreatureData" ofType:#"sqlite"]];
NSLog(#"JUST LOADED COREDATA SQLITE");
NSError* err = nil;
if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
NSLog(#"Oops, couldn't copy preloaded data");
}
}
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES };
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:#"UserSavedData" URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:#"StaticTableData" URL:secondStoreURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
Apple recommends that you separate static and user data, in this case into 2 persistent stores. You could do one of the following:
First and preferable option, you use a separate store for the user data. You need to update the data model to accommodate two stores, so it will be a lightweight migration.
You would then extract the user data on the first startup from the existing sqlite store and store them to your new dynamic store. You can then delete the old sqlite store.
Second option is to keep the current setup. On first run, copy your new file with a different name, then extract the user data from the old store and copy it to the new store. Then delete the old store.

Verify with delay and rejects take a lot of time to run tests

My project use OCMock, OHHTTPStubs and XCTest.
I try to test SDK (SDK implemented by my), so i stub Http response/requests and add some expectation on callback methods. Each unit tests have some expectations that delegate methods will be called properly and after setting all expectations i included rejects for each delegate method to be sure that only specified methods will be called and nothing more.
Example of my unit test:
// stub http
... here are some http stubs...
// expect
[[self.mockDelegate expect] didSomethigHappend:[OCMArg checkWithBlock:^BOOL(id obj) {
BOOL result = NO;
// testing parameter object
if(result) {
// call next method on SDK
[self.objectToTest nextMethod];
}
return result;
}] withError:[OCMArg isNil]];
// reject any other call:
[[self.mockDelegate reject] didSomethigHappend:[OCMArg any] withError:[OCMArg any]];
[[self.mockDelegate reject] dodSomethig2:[OCMArg any] withError:[OCMArg any]];
[[self.mockDelegate reject] dodSomethig3:[OCMArg any] withError:[OCMArg any]];
[super.objectToTest doSomethigWithDelegate:super.mockDelegate]; // run
[super.mockDelegate verifyWithDelay:3]; // verify
All tests pass successfully but it take a lot of time to run everything. But what i saw, when i remove those rejects all tests run 3 times faster.
After some debuging i check the implementation of OCMock library method:
- (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location
{
NSTimeInterval step = 0.01;
while(delay > 0)
{
if([expectations count] == 0)
break;
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:step]];
delay -= step;
step *= 2;
}
[self verifyAtLocation:location];
}
And where rejects are registered the "expectations" variable always contains those rejects so it waits all delay time.
Anybody have the same problem ?
Maybe i do something wrong and this is correct behaviour ?
It seams that OCMock have a bug.
Here is workaround with method swizzling:
// stubbing verify with delay from ocmock framework
// because ocmock if has any registered rejects
// waits whole specified time, so we need to change this flow
// so it will wait for all expectation has occure and after that wait some steps to make sure that any reject has not invoked
static dispatch_once_t swizzle_token;
dispatch_once(&swizzle_token, ^{
SEL originalSelector = #selector(verifyWithDelay:);
SEL swizzledSelector = #selector(fake_verifyWithDelay:);
Method originalMethod = class_getInstanceMethod([OCMockObject class], originalSelector);
Method swizzledMethod = class_getInstanceMethod([VDFUsersServiceBaseTestCase class], swizzledSelector);
BOOL didAddMethod =
class_addMethod([OCMockObject class],
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod([OCMockObject class],
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
And here is fake_verify method:
- (void)fake_verifyWithDelay:(NSTimeInterval)delay {
NSTimeInterval step = 0.1;
while (delay > 0) {
#try {
[self verify];
break;
}
#catch (NSException *e) {}
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:step]];
delay -= step;
step += 0.1;
}
[self verify];
}

UITableView: cell.textLabel.text error: [__NSArrayI isEqualToString:]

I'm downloading an NSManagedObject. Assigning an NSString to valueForKey#"username" of that object. Next, I assign that string to a cell.textLabel.text in UITableView and I'm getting an exception.
The following is the code thats throwing an exception:
-(NSString *)fetchUserName
{
if ([fetchedUserNameObject objectAtIndex:0]!= NULL)
{
recipientUser = (User*)(fetchedUserNameObject);
NSString *userName = (NSString*)[recipientUser valueForKey:#"username"];
return userName;
}
}
.....
NSString *recipientUserName = [self fetchUserName]
......
- (IBAction)reviewButtonPressed:(UIBarButtonItem *)sender
{
NSLog(#"Recipient UserName from Review Button %#", recipientUserName);
PDReviewVC *modalVC = [self.storyboard instantiateViewControllerWithIdentifier:#"pdReview"];
UINavigationController *navBar=[[UINavigationController alloc]initWithRootViewController:modalVC];
modalVC.recipientUserName= self.recipientUserName;
[self presentViewController:navBar animated:YES completion:NULL];
}
Log
2013-08-30 11:51:49.393 Time[1188:c07] Recipient UserName from Review Button (
iphone3gs
)
Now in PDReviewVC:
.h
#property (nonatomic, retain) NSString * recipientUserName;
.m
#synthesize recipientUserName;
...
//cellForRowAtIndexPath
if (indexPath.section == 0)
{
cell.textLabel.text = recipientUserName;
}
I get the following error on cell.textLabel.text = recipientUserName;:
2013-08-30 11:51:54.679 Time[1188:c07] -[__NSArrayI isEqualToString:]: unrecognized selector sent to instance 0xb889390
2013-08-30 11:53:53.303 Time[1188:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI isEqualToString:]: unrecognized selector sent to instance 0xb889390'
*** First throw call stack:
UPDATE
NSLog for the following code:
if ([fetchedUserNameObject objectAtIndex:0]!= NULL)
{
recipientUser = (User*)(fetchedUserNameObject);
NSLog(#"User %#", recipientUser);
NSString *userName = (NSString*)[recipientUser valueForKey:#"username"];
NSLog (#"userName from fetchUserName: %#", userName);
NSLog(#"%#", userName);
return userName;
}
else return NULL;
NSLog:
2013-08-30 12:36:29.495 Time[1341:c07] User (
"<User: 0xa55cd60> (entity: User; id: 0xa5a0ae0 <x-coredata://3B273CFB-1CAA-4FA0-95DC-BA9420219380-1341-000007F42366B21A/User/piphone3gs> ; data: <fault>)"
)
2013-08-30 12:36:29.496 Time[1341:c07] userName from fetchUserName: (
iphone3gs
)
2013-08-30 12:36:29.496 Time[1341:c07] (
iphone3gs
)
The answer to your question is right in your error message. :-)
It's telling you that the object you think is of type NSString is really NSArray. Thus, NSArray does not respond to any method called isEqualToString.
In your fetchUserName method you are casting an object as NSString and returning it, but apparently you are getting an NSArray here. When you set this object to your label's text property, something goes on behind the scenes to ask if the current string property is equal to the one you're trying to set. Then, error.
Try placing this line before you return in the fetchUserName method:
NSLog(#"%#",username);
return username;
Then modify your question with the console results of this NSLog() and we can help you figure out what is inside the array.
Ok, before you return the username object, do this:
NSString *username;
id object = [recipient valueForKey:#"username"];
if ([object isKindOfClass:[NSString class]]) {
username = (NSString *)object;
return username;
} else if ([object isKindOfClass:[NSArray class]]) {
NSArray *returnedArray = (NSArray *)object;
if (returnedArray.count > 0) {
id arrayMember = [returnedArray objectAtIndex:0];
if ([arrayMember isKindOfClass:[NSString class]]) {
username = (NSString *)arrayMember;
return username;
}
}
}
return nil;
You are casting [recipientUser valueForKey:#"username"] as an NSString to get your code to work, when it is in fact returning an NSArray. Remove the cast (NSString*) and get the code working so you are in fact pulling a string rather than an array from recipientUser.
To debug, I'd suggest changing your logging to:
if ([fetchedUserNameObject objectAtIndex:0]!= NULL)
{
recipientUser = (User*)(fetchedUserNameObject);
NSArray *userNames = [recipientUser valueForKey:#"username"];
for (int i=0; i < [userNames count]; i++) {
NSLog(#"Username %i is %#", i, [userNames objectAtIndex:i]);
}
}

AFNetworking Expected content type "application/xml", "text/xml", text/plain

I am trying to log into google reader using AfNetworking AfHttpClient but I am getting this error that I can;t seem to figure out.
Below is my subclass of AFNetworking:
// main url endpoints
#define GOOGLE_ACCOUNTS_BASE_URL #"https://www.google.com/accounts/"
#implementation ADPGoogleLoginClient
+ (ADPGoogleLoginClient *)sharedClient {
static ADPGoogleLoginClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[ADPGoogleLoginClient alloc] initWithBaseURL:[NSURL URLWithString:GOOGLE_ACCOUNTS_BASE_URL]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFXMLRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Content-type" value:#"text/plain"];
[self setDefaultHeader:#"Accept" value:#"text/plain"];
return self;
}
#end
And then I try to form a request by using the following code:
//set up request params
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
#"googlereader-ios-client", #"client",
[keychainCredentials objectForKey:(__bridge id)kSecAttrAccount], #"Email",
[keychainCredentials objectForKey:(__bridge id)kSecValueData], #"Passwd",
#"reader", #"service",
#"ipad", #"source", nil];
//make requests
[[ADPGoogleLoginClient sharedClient] getPath:#"ClientLogin"
parameters:params
success:^(AFHTTPRequestOperation *operation , id responseObject)
{
//parse out token and store in keychain
NSString* responseString = [operation responseString];
NSString* authToken = [[[responseString componentsSeparatedByString:#"\n"] objectAtIndex:2]
stringByReplacingOccurrencesOfString:#"Auth=" withString:#""];
keychainToken = [[KeychainItemWrapper alloc] initWithIdentifier:#"GReaderToken" accessGroup:nil];
[keychainToken setObject:authToken forKey:(__bridge id)kSecValueData];
loginSuccess();
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"There was an error logging into Reader - %#", [error localizedDescription]);
loginFailure(error);
}];
Im setting the default headers to
[self setDefaultHeader:#"Content-type" value:#"text/plain"];
[self setDefaultHeader:#"Accept" value:#"text/plain"];
so im not sure why it still thinks it is expecting xml?
You are setting the Content-Type header for the request, but the error you're seeing is from the response. In order for requests to be considered "successful", the content type needs to match up with expectations (so as to avoid trying to parse a JSON response when you expected XML).
In that same error code, it should have mentioned what content type it actually got back. If it is indeed XML, add that using AFXMLRequestOperation +addAcceptableContentTypes:, and everything should work just fine.
Hi Matt so I just got around to testing this. Looks like I was registering the wrong AF operation. I changed
[self registerHTTPOperationClass:[AFXMLRequestOperation class]];
to
[self registerHTTPOperationClass:[AFHTTPRequestOperation class]];
and all was good!

UILocalNotification nil userInfo

I try to cancel a Local notification. I attach a dict with a Id for locate it later:
+ (void) send:(NSString*)title actionText:(NSString *)actionText when:(NSDate *)when count:(NSInteger)count option:(NSDictionary *)options
{
UILocalNotification *notif = [[UILocalNotification alloc] init];
//Setup code here...
notif.userInfo = options;
ALog(#"Sending notification %# %#", notif.alertBody, notif.userInfo);
//Print: Sending notification Task Col 0 0 {id = "1-1"};
[[UIApplication sharedApplication] scheduleLocalNotification:notif];
}
Then when I try to locate it:
+ (void) cancelNotification:(NSString *)theId {
for(UILocalNotification *aNotif in [[UIApplication sharedApplication] scheduledLocalNotifications])
{
if([[aNotif.userInfo objectForKey:#"id"] isEqualToString:theId])
{
// Never come here: userInfo is nil!!!
}
}
}
Always the userInfo is nil. I send the dict:
NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
[info setObject:theId forKey:#"id"];
or
[NSMutableDictionary dictionaryWithObject:theId forKey:#"id"]
with the same result. (theId is NSString)
The local notification valid only, it has valid timestamp. So while you are creating make sure that, it has valid timestamp. So that, it will be present in scheduledNotifications of UIApplicaiton.
Please let me know your comments.
eg: localNotification.fireDate = [NSDate date];

Resources