Unable to mock NSUserDefaults - ios

I don't seem to be able to mock NSUserDefaults. I want it to send back the data that I tell it too but instead, it sends back the data stored in it from when running the app.
I am using:
Specta
Expecta
OCMockito
I have the following test:
describe(#"AppDelegate", ^{
__block AppDelegate *appDelegate;
beforeEach(^{
appDelegate = [AppDelegate new];
});
afterEach(^{
appDelegate = nil;
});
describe(#"application did finish launching with options", ^{
beforeEach(^{
[appDelegate application:nil didFinishLaunchingWithOptions:nil];
NSUserDefaults *mockUserDefaults = mock([NSUserDefaults class]);
[given([mockUserDefaults objectForKey:#"currentUser"]) willReturn:nil];
});
it(#"should have a login view controller as root view controller", ^{
expect(appDelegate.window.rootViewController).to.beKindOf([ToALoginViewController class]);
});
});
});
So the above test fails because it actually returns some data for currentUser. What am I doing wrong?
Following on from what Ken Kuan said, I made the following changes:
AppDelegate.h
#property (strong, nonatomic) NSUserDefaults *userDefaults;
- (void)setUserDefaults:(NSUserDefaults *)userDefaults;
AppDelegate.m
- (void)setUserDefaults:(NSUserDefaults *)userDefaults {
_userDefaults = userDefaults;
}
AppDelegateSpec.m
beforeEach(^{
[appDelegate application:nil didFinishLaunchingWithOptions:nil];
NSUserDefaults *mockUserDefaults = mock([NSUserDefaults class]);
[appDelegate setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentUser"]) willReturn:nil];
});
However, I still get the same problem.

You can mock NSUserDefaults. I have this code which is working fine.
When you are mocking NSUserdefaults make sure you are stopping mock at the end of test case, otherwise it would cause errors in other places where you are using NSUserDefaults.
id userDefaultsMock = OCMClassMock([NSUserDefaults class]);
OCMStub([userDefaultsMock standardUserDefaults]).andReturn(userDefaultsMock);
OCMStub([[userDefaultsMock standardUserDefaults] boolForKey:SHOWMYNAME]).andReturn(#"N");
[self.myObject codewithuserdefaults];
OCMVerify( [[NSUserDefaults standardUserDefaults] setValue:[OCMArg any] forKey:FIRSTNAME]);
[userDefaultsMock stopMocking];

You must use [NSUserDefaults standardUserDefaults] in appDelegate which is actual user defaults, not your mock.
A solution is to make user default to a property of app delegate and set it with your mockUserDefaults in tests.
Another one is to swizzle [NSUserDefaults standardUserDefaults] to return your mockUserDefaults in your tests.

So with much thanks to Ken Kuan, I managed to solve the problem I was having. Heres how I achieved it.
I added a NSUserDefaults property to the AppDelegate and a setter function.
I set self.userDefaults to [NSUserDefaults standardUserDefaults] in application:willFinishLaunchingWithOptions:.
I then set the rootViewController in application:didFinishLaunchingWithOptions:
AppDelegate.h
#property (strong, nonatomic) NSUserDefaults *userDefaults;
- (void)setUserDefaults:(NSUserDefaults *)userDefaults;
AppDelegate.m
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.userDefaults = [NSUserDefaults standardUserDefaults];
return YES;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
NSData *encodedUserData = [self.userDefaults objectForKey:#"currentUser"];
if (encodedUserData) {
NSLog(#"Have current user");
self.window.rootViewController = [ThermostatViewController new];
} else {
NSLog(#"No current user");
self.window.rootViewController = [ToALoginViewController new];
}
[self.window makeKeyAndVisible];
return YES;
}
- (void)setUserDefaults:(NSUserDefaults *)userDefaults {
_userDefaults = userDefaults;
}
AppDelegateSpec.m
describe(#"application will finish launching with options", ^{
beforeEach(^{
[appDelegate application:nil willFinishLaunchingWithOptions:nil];
});
context(#"application did finish launching with options and with no current user", ^{
beforeEach(^{
NSUserDefaults *mockUserDefaults = mock([NSUserDefaults class]);
[appDelegate setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentUser"]) willReturn:nil];
[appDelegate application:nil didFinishLaunchingWithOptions:nil];
});
it(#"should have a login view controller as root view controller", ^{
expect(appDelegate.window.rootViewController).to.beKindOf([ToALoginViewController class]);
});
});
});

Related

How to open an existing viewcontroller in ios

I need to open existing viewcontroller from AppDelegate while receiving push notification. Currently i am opening new one every time so issue is that it is called viewDidLoad every time and all variable are reinitialized again and again.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[[NSUserDefaults standardUserDefaults] setObject:#"Yes" forKey:#"Got Message"];
[[NSUserDefaults standardUserDefaults] setObject:userInfo forKey:#"message"];
[[NSUserDefaults standardUserDefaults]synchronize];
HomeViewController* room = [[HomeViewController alloc] init];
[self.window.rootViewController presentViewController:room
animated:NO
completion:nil];
}
Try to do so:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[[NSUserDefaults standardUserDefaults] setObject:#"Yes" forKey:#"Got Message"];
[[NSUserDefaults standardUserDefaults] setObject:userInfo forKey:#"message"];
[[NSUserDefaults standardUserDefaults]synchronize];
[self.window.rootViewController presentViewController:self.room
animated:NO
completion:nil];
}
- (UIViewController *)room {
if (_room == NULL) {
_room = [[HomeViewController alloc] init];
}
return _room;
}
Then you can reuse your view controller (However, it will exposure the view controller in your AppDelegate, which may be a taste in clean code).
Since you want to use a existing view controller, why do you use the code HomeViewController* room = [[HomeViewController alloc] init];?
Follow your goal, my suggestion is use a property to retain the existing view controller, just like:
#property (strong, nonatomic) UIViewController *existingViewController;
and you
[self.window.rootViewController presentViewController:existingViewController
animated:NO
completion:nil];
You can get UINavigationController in AppDelegate meethod
UIViewController *yourViewController = //your view controller to show after push
UINavigationController *navController = self.window.rootViewController;
[navController popToViewController:yourViewController animated:YES]

The error webview on ios

My application is webview.
The normal it load page http://staging.nhomxe.vn.
But when server send a notify, and attachmented a link. Webview will open this link.
My application activity normal. Then, i add Navigation Controller to ViewControler.
When Server send notify, my webview notify error at [vc.webView loadRequest:urlRequest]; , and application auto exit.
My code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound)];
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (localNotif) {
// launched from notification
NSLog(#"Co notify!!!");
NSString *message = [localNotif valueForKey:#"link"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:message forKey:#"LINK"];
} else {
// from the springboard
NSLog(#"Khong co notify!!!");
}
return YES;
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
}
- (void) application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(#"My token: %#",deviceToken);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:deviceToken forKey:#"TOKEN"];
}
- (void) application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
NSLog(#"Error: %#",error);
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
- (void)application:(UIApplication*)application didReceiveRemoteNotification:
(NSDictionary*)userInfo
{
NSURL *url ;
NSLog(#"Received notification: %#", userInfo);
NSDictionary *data = [ userInfo objectForKey:#"aps"];
for(NSString *key in data) {
NSString *info = [data objectForKey:key];
NSLog(#"thong tin nhan dc: %# : %#", key, info);
}
NSString *message = [userInfo valueForKey:#"link"] ;
//NSArray *info = [message componentsSeparatedByString:#"&#"];
//NSString *body = [info objectAtIndex:0];
//NSString *link = [info objectAtIndex:1];
NSLog(#"Thong tin Link: %#",message);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:message forKey:#"LINK"];
ViewController *vc = (ViewController *)self.window.rootViewController;
if(message == NULL)
{
url = [NSURL URLWithString:#"http://staging.nhomxe.vn"];
}else
{
url = [NSURL URLWithString:message];
}
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[vc.webView loadRequest:urlRequest];
[vc.webView3 loadRequest:urlRequest];
}
#end
My error:
2014-08-28 14:51:59.374 NhomXe[30379:907] Thong tin Link: http://staging.nhomxe.vn/org/instance_message/conversation-detail.xhtml?post=13001628&orgId=190000168#vehicletracking
2014-08-28 14:52:14.950 NhomXe[30379:907] -[UINavigationController webView]: unrecognized selector sent to instance 0x1d5c8350
2014-08-28 14:52:14.958 NhomXe[30379:907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationController webView]: unrecognized selector sent to instance 0x1d5c8350'
*** First throw call stack:
(0x314e22a3 0x3913e97f 0x314e5e07 0x314e4531 0x3143bf68 0xd687f 0x3353d585 0x3353dfa5 0x33f53305 0x314b7173 0x314b7117 0x314b5f99 0x31428ebd 0x31428d49 0x34fa52eb 0x3333e301 0xd6ad9 0x39575b20)
libc++abi.dylib: terminate called throwing an exception
Your rootViewController is returning a UINavigationController which is used to manage a navigation stack, that is represented by an array of view controllers.
To get your view controller try getting the topViewController, for example:
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
ViewController *vc = (ViewController *)navigationController.topViewController;
You can also get a array of all view controller that navigation manages by:
NSLog(#"%#", navigationController.viewControllers);
Update your UI on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
// make sure vc is a weak refernace to avoid retain cycle
[vc.webView loadRequest:urlRequest];
[vc.webView3 loadRequest:urlRequest];
});

Trying to save current data in an NSMutableArray on exit, and load it on launch

I'm using an NSMutableArray to store data from my UITableView, and I'd like to store the data in NSUserDefaults on the application didEnterBackground as well as willTerminate. Here's how I'm doing this so far:
Delegate:
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
EXViewController *main = [[EXViewController alloc] init];
[[NSUserDefaults standardUserDefaults] setObject:main.data forKey:#"dataKey"];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
EXViewController *main = [[EXViewController alloc] init];
[[NSUserDefaults standardUserDefaults] setObject:main.data forKey:#"dataKey"];
}
main.data is an NSMutableArray.
I forgot how to do this, being the fact that I haven't worked with table views in some time. Any help is appreciated, thanks!
Full delegate code (.h):
#import <UIKit/UIKit.h>
#class EXViewController;
#interface EXAppDelegate : UIResponder <UIApplicationDelegate> {
EXViewController *_main;
NSString *title;
}
// Data to be added
#property (nonatomic, retain) NSString *title;
// Default properties
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) EXViewController *viewController;
#end
And (.m):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_main = [[EXViewController alloc] init];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[EXViewController alloc] initWithNibName:#"EXViewController" bundle:nil];
// Create navigation controller
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.viewController];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
You are allocating and initializing a new instance of EXViewController everytime - you should probably reference the single instance in your app delegate and then save/restore that data.
EDIT
// In your AppDelegate.m - a lot of code ommitted
// NOTE: make sure that self.viewController points to self->_main
// This must be in the #implementation block
#synthesize viewController = _main;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// NOTE: Notice the removal of the _main = ...
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
// NOTE: This is the only view controller you should reference.
self.viewController = [[EXViewController alloc] initWithNibName:#"EXViewController" bundle:nil];
// Create navigation controller
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.viewController];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
[[NSUserDefaults standardUserDefaults] setObject:self.viewController.data forKey:#"dataKey"];
}
// You will also need to restore it when needed
Saving
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// saving an NSString
[prefs setObject:#"TextToSave" forKey:#"keyToLookupString"];
// saving an NSInteger
[prefs setInteger:42 forKey:#"integerKey"];
// saving a Double
[prefs setDouble:3.1415 forKey:#"doubleKey"];
// saving a Float
[prefs setFloat:1.2345678 forKey:#"floatKey"];
// This is suggested to synch prefs, but is not needed (I didn't put it in my tut)
[prefs synchronize];
Retrieving
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// getting an NSString
NSString *myString = [prefs stringForKey:#"keyToLookupString"];
// getting an NSInteger
NSInteger myInt = [prefs integerForKey:#"integerKey"];
// getting an Float
float myFloat = [prefs floatForKey:#"floatKey"];
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
[[NSUserDefaults standardUserDefaults] setObject:self.data forKey:#"dataKey"];
[[NSUserDefaults standartUserDefaults] syncronize];
}
Where data is a property.

Pull data from view controller on a function

Passing data just isn't doing the trick...I need to pull string data from a view controller when a function in the AppDelegate is called. Here's the code:
In App Delegate:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
AMViewController *viewController = [[AMViewController alloc] initWithNibName:#"AMViewController" bundle:nil];
self.xpData = viewController.xpLabel.text;
NSLog(#"Value of xpString in AD: %#", self.xpData);
}
In my ViewController, I'm not using an action to pass/retrieve the string. I'm basically pulling it from the ViewController when the user hits the home button. I'm doing this because I'd like to save my data on exit. Thanks!
In your AppDelegate.h
#property (atomic, copy) NSString *yourString;
Where you have changed xpLabel.text, do this
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.yourString = xpLabel.text;
Now in
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"Value of xpString in AD: %#", self.yourString);
}
Another Method
Set your value in AMViewController class like this
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:xpLabel.text forKey:#"yourkey"];
[prefs synchronize];
Get your Value in applicationDidEnterBackground like this
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
yourString = [prefs stringForKey:#"yourkey"];
Did you tried working with NSNotificationCenter
Or shared delegate:
http://www.roostersoftstudios.com/2011/04/12/simple-delegate-tutorial-for-ios-development/
http://coderchrismills.wordpress.com/2011/05/05/basic-delegate-example/

viewWillAppear is not called in my view controller when the app becomes active

Disclaimer: I'm an iOS noob.
I created a view based app using Xcode's template that uploads pictures to a website. Xcode created AppDelegate.m (& .h) and ViewController.m as well as a storyboard for me automatically. The app is launched from a website using a URL schema, and I use that information in my view controller. I have two questions:
1) Is this a good way to pass query strings from my AppDelegate to the view controller?
I have a NSDictionary property in my AppDelegate called queryStrings which stores the query strings. Then in viewWillAppear in my view controller I use the following code to access it:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSDictionary *queryStrings = appDelegate.queryStrings;
wtID = [queryStrings valueForKey:#"wtID"];
2) When the app is already launched but in background and the user opens the app again via a url on the webpage (with different query string), I have implemented openURL in the AppDelegate which populates my queryStrings property in the AppDelegate. Problem is: the view controller's viewWillAppear method is never called so the above code isn't executed. How do I get the query string data to the view controller? How does the view controller know the app just became active?
NOTE: Because the project was created using the "view-based app" template in Xcode, the only property my AppDelegate has is the window property. I can't tell where in code that my view controller is ever even referenced by the AppDelegate... It's never set as the rootViewController or anything in the code anyway... I guess it's just some IB magic that I can't see.
In case it helps, here's my AppDelegate.h:
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) NSDictionary *queryStrings;
#end
and here's my didFinishLoadingWithOptions, openURL, and my helper method: parseQueryString:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[[JMC sharedInstance] configureJiraConnect:#"https://cmsmech.atlassian.net/" projectKey:#"WTUPLOAD" apiKey:#"7fc060e1-a795-4135-89c6-a7e8e64c4b13"];
if ([launchOptions objectForKey:UIApplicationLaunchOptionsURLKey] != nil) {
NSURL *url = [launchOptions objectForKey: UIApplicationLaunchOptionsURLKey];
NSLog(#"url received: %#", url);
NSLog(#"query string: %#", [url query]);
NSLog(#"host: %#", [url host]);
NSLog(#"url path: %#", [url path]);
queryStrings = [self parseQueryString:[url query]];
NSLog(#"query dictionary: %#", queryStrings);
}
else {
queryStrings = [self parseQueryString:#"wtID=nil"];
}
return YES;
}
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
NSLog(#"openURL executed");
if (!url)
return NO;
NSLog(#"query string: %#", [url query]);
queryStrings = [self parseQueryString:[url query]];
mainViewController.wtID = [queryStrings valueForKey:#"wtID"];
return YES;
}
-(NSDictionary *)parseQueryString:(NSString *)query
{
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:6];
NSArray *pairs = [query componentsSeparatedByString:#"&"];
for (NSString *pair in pairs)
{
NSArray *elements = [pair componentsSeparatedByString:#"="];
NSString *key = [[elements objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *val = [[elements objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:val forKey:key];
}
return dictionary;
}
Thanks in advance!
So, storyboards are starting to get on my nerves, and I'm not even building your app! :)
As I have stated in your other question (of which this is a duplicate, but hey), the lack of the reference to the view controller is due to storyboards. viewDidAppear and viewWillAppear get called when the view is added to the hierarchy. Going to and from the background and foreground doesn't qualify as being added to the hierarchy. Luckily, your app sends notifications that correspond to the methods in the AppDelegate. Simply register as an observer of the UIApplicationWillEnterForegroundNotification notification. Then you can update your UI in the method that gets fired as a result of receiving that notification!
EDIT: Adding the link to the duplicated question for good measure: How do I access my viewController from my appDelegate? iOS
Hm, so why there is no UIViewController declared in your AppDelegate?
UPDATE
When you created a new project, you should have smth like this in your AppDelegate:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UIViewController *viewController;
#end
//
#import "ViewController.h"
#implementation AppDelegate
#synthesize window, viewController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc] init];
[self.window setRootViewController:self.viewController];
[self.window makeKeyAndVisible];
return YES;
}
//...
#end

Resources