iOS TestCase for method which includes NS Timer [duplicate] - ios

This question already has answers here:
XCTest and asynchronous testing in Xcode 6
(4 answers)
Closed 7 years ago.
I have a IBAction in my view controller which looks like this
-(IBAction)signUpAction:(id)sender
{
AppDelegate *appDel = [[UIApplication sharedApplication]delegate];
//check for internet Connection
if(appDel.isReachable)
{
//Internet Connection available
//perform animation od buttons and imagie view
[self fallDownAnimation];
//after animation perform model segue to corresponding view controller
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:0.8f target:self selector:#selector(performRegistrationPageSegue) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
else
{
//No internet Connection
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:ALERT_VIEW_TITLE message:#"No Internet Connection" delegate:self cancelButtonTitle:nil otherButtonTitles:#"Okay", nil];
[alert show];
}
}
-(void)performRegistrationPageSegue{
[self performSegueWithIdentifier:#"registerVCSegue" sender:self];
}
I want to write a test case on signUpAction method and verify if the Segue is performed. Since it has a timer the test case i have written is failing.
I Need a way to test the following condition
My Current Testcase method is
-(void)testRegisterViewControllerSegueOnAvailableInternetConnection{
AppDelegate *appDel = [[UIApplication sharedApplication]delegate];
appDel.isReachable = YES;
id loginMock = [OCMockObject partialMockForObject:_initialViewControllerToTest];
[[loginMock expect] performSegueWithIdentifier:#"registerVCSegue" sender:[OCMArg any]];
[loginMock performSelectorOnMainThread:#selector(signUpAction:) withObject:_initialViewControllerToTest.signUpButton waitUntilDone:YES];
XCTAssert([loginMock verify],#"Segue to Register Page not Performed on Sign Up Click");
}

You need to enter the event loop for a period of time, so that the timer event can be processed. It is basically not possible to fully regression test code without doing this. Here is a simplified method:
// Wait inside the event loop for a period of time indicated in seconds
+ (void) waitFor:(NSTimeInterval)maxWaitTime
{
int numSeconds = (int) round(maxWaitTime);
if (numSeconds < 1) {
numSeconds = 1;
}
for ( ; numSeconds > 0 ; numSeconds--) #autoreleasepool {
const int maxMS = 1000;
const int incrMS = 1;
const double seconds = 1.0 / (maxMS / incrMS);
for (int ms = 0 ; ms < maxMS; ms += incrMS) #autoreleasepool {
// One pass through the run loop for each time interval
NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:seconds];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:maxDate];
}
}
return;
}
A more complex impl with a selector that can be called to return when a test condition is true:
+ (BOOL) waitUntilTrue:(id)object
selector:(SEL)selector
maxWaitTime:(NSTimeInterval)maxWaitTime
{
NSAssert(object, #"object is nil");
NSAssert(selector, #"selector is nil");
NSMethodSignature *aSignature = [[object class] instanceMethodSignatureForSelector:selector];
NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
[anInvocation setSelector:selector];
[anInvocation setTarget:object];
// Invoke test condition method once before the timing loop is entered, so that the
// event loop will not be entered if the condition is initially TRUE.
BOOL state;
[anInvocation invoke];
[anInvocation getReturnValue:&state];
if (state) {
return TRUE;
}
// The condition is FALSE, so enter the event loop and wait for 1 second
// each iteration through the loop. The logic below makes sure that the
// 1 second wait will be done at least once, even if wait time is less
// than a full second.
int numSeconds = (int) round(maxWaitTime);
if (numSeconds < 1) {
numSeconds = 1;
}
for ( ; numSeconds > 0 ; numSeconds--) #autoreleasepool {
NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
[[NSRunLoop currentRunLoop] runUntilDate:maxDate];
[anInvocation invoke];
[anInvocation getReturnValue:&state];
if (state) {
return TRUE;
}
}
return FALSE;
}

Related

iOS: Why can I not call performSelector to execute the same function where it is called?

When I use [self performSelector:#selector(checkFirstSynchro) withObject:nil afterDelay:30.0]; in the checkFirstSynchro function, it seems not to work. Is it because it is in a Class function?
-(void) viewDidLoad
{
...
[self checkFirstSynchro]
...
}
- (void) checkFirstSynchro
{
//Check if firstSynchro
BOOL firstSynchro = [[[NSUserDefaults standardUserDefaults] valueForKey:#"FirstSynchro"] boolValue];
if (!firstSynchro)
{
[Reachability checkInternetConnectivityWithSuccessCompletion:^(NSError *error) //test if there is internet c
onnection
{
if (error == nil)
{
[self sync:connectBtn];
}
else
{
[self performSelector:#selector(checkFirstSynchro) withObject:nil afterDelay:30.0];
}
}];
}
}
It launches well the function, but it doesn't execute the perform 30.0 seconds later.
I tried this instead of the performSelector, but without success:
[NSTimer scheduledTimerWithTimeInterval:TIMER_FIRST_SYNCHRO
target:self
selector:#selector(checkFirstSynchro)
userInfo:nil
repeats:NO];
It launches well the function, but it doesn't execute the [NSTimer scheduledTimerWithTimeInterval 30.0 seconds later.
Thanks in advance.

how to resume time counter value in when i start the app

I want to resume time counter value in when I start the app.like first my label value is 05:00 than after close the app but when I start the app agin that time timer count start to 05:00. Please help me
int timeSec,timeMin ;
- (void)viewDidLoad {
timeSec=0;
timeMin=0;
[self StartTimer];
}
-(void) StartTimer
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
//Event called every time the NSTimer ticks.
- (void)timerTick:(NSTimer *)timer
{
timeSec++;
if (timeSec == 60)
{
timeSec = 0;
timeMin++;
}
//Format the string 00:00
NSString* timeNow = [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];
//Display on your label
//[timeLabel setStringValue:timeNow];
self.lbl_timer.text= timeNow;
}
//Call this to stop the timer event(could use as a 'Pause' or 'Reset')
- (void) StopTimer
{
// [timer invalidate];
timeSec = 0;
timeMin = 0;
//Since we reset here, and timerTick won't update your label again, we need to refresh it again.
//Format the string in 00:00
NSString* timeNow = [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];
//Display on your label
// [timeLabel setStringValue:timeNow];
self.lbl_timer.text= timeNow;
}
thanks for advance..
Try this code.store data in nsuserdefault
- (void)viewDidLoad {
timeSec=[[NSString stringWithFormat:#"%ld",(long)[[NSUserDefaults standardUserDefaults]integerForKey:#"timeSec"]] intValue];
timeMin=[[NSString stringWithFormat:#"%ld",(long)[[NSUserDefaults standardUserDefaults]integerForKey:#"timeMin"]] intValue];
[self StartTimer];
}
-(void) StartTimer
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
self.lbl_timer.text=#"00:00";
}
- (void)timerTick:(NSTimer *)timer
{
timeSec++;
if (timeSec == 60)
{
[locationManager startUpdatingLocation];
timeSec = 0;
timeMin++;
}
self.lbl_timer.text= [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];;
}
when you back to the screen that time add this code.like back button or close button action
[timer invalidate];
[[NSUserDefaults standardUserDefaults]setInteger:timeSec forKey:#"timeSec"];
[[NSUserDefaults standardUserDefaults]setInteger:timeMin forKey:#"timeMin"];
Declare two variable in appdelegate file
1)NSUserDefaults *usedefaults;
2)#property (nonatomic,readwrite) int timeSec,timeMin,isBackground;
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.isBackground=1;
usedefaults=[NSUserDefaults standardUserDefaults];
[usedefaults setObject:[NSString stringWithFormat:#"%d",self.timeMin] forKey:#"timemin"];
[usedefaults setObject:[NSString stringWithFormat:#"%d",self.timeSec] forKey:#"timesec"];
NSLog(#"Enter in back value userdefault %#",usedefaults);
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
if(self.isBackground==1)
{
self.isBackground=0;
self.timeMin=[[usedefaults objectForKey:#"timemin"] intValue];
self.timeSec=[[usedefaults objectForKey:#"timesec"] intValue];
NSLog(#"Foreground value min %d sec %d",self.timeMin,self.timeSec);
}
}
In ViewDidload in your Timer startpage
app=(AppDelegate *)[[UIApplication sharedApplication] delegate];
finally just replace you self.timemin to app.timemin same for timesec.
Cheers enjoy That surely work for u ..

SocketScan always returns 100% battery life

I'm getting 100% as the result every time...why (iOS/ SocketScan API v.10.2.227)?
Here's my code:
-(void) onGetBatteryInfo:(ISktScanObject*)scanObj {
SKTRESULT result=[[scanObj Msg]Result];
if(SKTSUCCESS(result)){
long batteryLevel = SKTBATTERY_GETCURLEVEL([[scanObj Property] getUlong]);
NSLog(#"BatteryInfo %ld", batteryLevel);
[self setBatteryLevel:batteryLevel];
[self.tableView reloadData];
} else {
NSLog(#"ES-GetBatteryInfo set status returned the error %ld",result);
}
}
Thanks,
Mark
Can you double check the following...
In viewDidLoad you create ScanApiHelper and a timer
if(ScanApi==nil) {
ScanApi=[[ScanApiHelper alloc]init];
[ScanApi setDelegate:self];
[ScanApi open];
ScanApiConsumer=[NSTimer scheduledTimerWithTimeInterval:.2 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
Your timer calls doScanApiReceive
-(void)onTimer: (NSTimer*)theTimer{
if(theTimer==ScanApiConsumer){
[ScanApi doScanApiReceive];
}
}
Finally, you don't query the battery level until after you've received an onDeviceArrival notification
-(void)onDeviceArrival:(SKTRESULT)result device:(DeviceInfo*)deviceInfo {
[ScanApi postGetBattery:deviceInfo Target:self Response:#selector(onGetBatteryInfo:)];
}

NSTimer does not stop when invalidate

I want to check if the application is idle (user doesn't take any action for the last 30 minutes) and log out the user.
For that I have an event manager that resets a timer.
- (void)sendEvent:(UIEvent *)event {
if(event == nil) {
[self resetIdleTimer];
} else {
[super sendEvent:event];
// Only want to reset the timer on a Began touch or an Ended touch, to reduce the number of timer resets.
NSSet *allTouches = [event allTouches];
if ([allTouches count] > 0) {
// allTouches count only ever seems to be 1, so anyObject works here.
UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded)
[self resetIdleTimer];
}
}
}
- (void)resetIdleTimer {
if (self.idleTimer) {
[self.idleTimer invalidate];
self.idleTimer = nil;
}
NSInteger maxTime = 60*30; //30 minutes
self.idleTimer = [NSTimer scheduledTimerWithTimeInterval:maxTime target:self selector:#selector(checkIfIdleTimerExceeded) userInfo:nil repeats:NO];
}
- (void)checkIfIdleTimerExceeded {
theProfileManager = [[ProfileManager alloc] init];
theProfile = [theProfileManager getProfile];
if( ! [[theProfile getStatus]isEqualToString:#"u1"]) {
if( ! [[theProfile getTheUser] isUnlimitedLogin]) {
theProfile = nil;
theUserManager = [[UserManager alloc] init];
[theUserManager setCurrentUser:[theProfile getTheUser]];
[theUserManager setCurrentUserAccount:[[theProfile getTheUser] getTheUserAccount]];
[theUserManager setCurrentUserSettings:[[theProfile getTheUser] getTheUserSettings]];
[theUserManager logoutCurrentUser];
[[MenuItemDataManager alloc] deleteJsonData];
NSNotification *msg = [NSNotification notificationWithName:#"leftPanelMsg" object:[[NSString alloc] initWithFormat:#"Home"]];
[[NSNotificationCenter defaultCenter] postNotification:msg];
[self performSelector:#selector(loadHomeView:) withObject:nil afterDelay:0.5f];
}
}
[self resetIdleTimer];
}
The checkIfIdleTimerExceeded does the log out process.
Problem: After 15 minutes I touch the screen, the resetIdleTime is called and should restart a new NSTimer. But after 15 minutes the application logs the user out.
Thanks for your help.
André.
As per your requirement of the Touches event. You should do this:
NSSet *allTouchEvents = [event allTouches];
if ([allTouchEvents count] > 0) {
// allTouchEvents count only ever seems to be 1, so anyObject works here.
UITouchPhase phase = ((UITouch *)[allTouchEvents anyObject]).phase;
if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded)
[self resetIdleTimer];
}
And in the resetIdleTimer, The
self.idleTimer = [NSTimer scheduledTimerWithTimeInterval:maxTime target:self selector:#selector(checkIfIdleTimerExceeded) userInfo:nil repeats:NO];
should be like
self.idleTimer = [NSTimer scheduledTimerWithTimeInterval:maxTime target:self selector:#selector(checkIfIdleTimerExceeded:) userInfo:nil repeats:NO];
And checkIfIdleTimerExceeded should be like:
-(void) checkIfIdleTimerExceeded :(NSTimer*)timer
{
//Do your all process and invalidate after completion
[timer invalidate];
}
I'm not sure which one of your other methods can be called beside the –resetIdleTimer in your workflow when you touch the screen, but that solution works to me as you have described to like getting it worked:
I'm seeing in the log console the –resetTimer: will called if I tap a button within 7 secs, it resets the timer properly and creates a new 7 secs timer.
the logout method will call only if I don't touch my button on the screen in 7 secs time, and the timer finally invokes the –logout: method.
- (void)resetTimer:(NSTimer *)timer {
NSLog(#"reset time...");
if (timer.isValid) [timer invalidate], _timer = nil;
_timer = [NSTimer scheduledTimerWithTimeInterval:7.f target:self selector:#selector(logout:) userInfo:nil repeats:NO];
}
//
- (void)buttonTouchedUpInside:(UIButton *)sender {
NSLog(#"touched...");
[self resetTimer:_timer];
}
//
- (void)logout:(NSTimer *)timer {
NSLog(#"logout...");
if (timer.isValid) [timer invalidate], _timer = nil;
// logout ...
}

iOS Logic Unit Test target crashes after unit test passes with NSTimer

I want a timer class that can post messages to a delegate when there are 1/2/3 seconds to go.
My test target consistently crashes.
iOS logic unit test target.
Tests class that times a duration using a repeating NSTimer
One test with no asserts. The test passes, but then the target crashes with:
/Developer/Tools/RunPlatformUnitTests.include: line 415: 770 Bus error "${THIN_TEST_RIG}" "${OTHER_TEST_FLAGS}" "${TEST_BUNDLE_PATH}"
It seems to me that it's some kind of memory allocation error, but I can't figure out what I'm missing. The problem is associated with the stop timer routine somehow. It's only when the timer runs out that the target crashes.
Things I've tried
Build and Analyze - no errors reported
Remove -framework and UIKit from the linker flags
Removing dealloc - this has no effect
Test Code
-(void)testGivenThreeSecondDurationAtOneSecondDelegateShouldBeToldToShowGreenCard {
JGTimerController *timer = [JGTimerController timerWithDurationValue:1 delegate:nil];
[timer startTimer];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.1]];
}
Class Code
#interface JGTimerController : NSObject {
NSNumber *duration;
NSTimer *timer;
id <NSObject, JGTimerControllerDelegate> _delegate;
}
#property (nonatomic, retain) NSNumber *duration;
... public methods...
#end
#implementation JGTimerController
#synthesize duration;
+(JGTimerController *)timerWithDurationValue:(NSUInteger)durationValue delegate:(id <JGTimerControllerDelegate>)delegate_ {
JGTimerController *instance = [[[JGTimerController alloc] initWithDurationValue:durationValue delegate:delegate_] autorelease];
return instance;
}
-(JGTimerController *)initWithDurationValue:(NSUInteger)durationValue delegate:(id <JGTimerControllerDelegate>)delegate_ {
self = [super init];
timer = nil;
[self setDurationValue:durationValue];
_delegate = delegate_;
return self;
}
-(NSUInteger)durationValue {
NSNumber *result = [self duration];
return result ? [result intValue] : 0;
}
-(void)setDurationValue:(NSUInteger)value_ {
[self setDuration:[NSNumber numberWithInt:value_]];
}
-(BOOL)stopTimerAtZeroDuration:(NSTimer *)timer_ {
if ([self durationValue] == 0) {
[self stopTimer];
return YES;
}
return NO;
}
-(void)startTimer {
if ([self stopTimerAtZeroDuration:nil])
return;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerDidCountDownByASecond:) userInfo:nil repeats:YES];
}
-(void)stopTimer {
if ([self durationValue] == 0 && [_delegate conformsToProtocol:#protocol(JGTimerControllerDelegate)])
[_delegate showRedCard];
[timer invalidate];
[timer release];
}
-(BOOL)timerIsRunning {
return (timer != nil);
}
-(void)timerDidCountDownByASecond:(NSTimer *)timer_ {
[self setDurationValue:[self durationValue] - 1];
[self stopTimerAtZeroDuration:timer_];
}
-(void)dealloc {
[_delegate release];
[timer release];
[duration release];
[super dealloc];
}
#end
There should be no [_delegate release] in dealloc because you did not retain it.
Likewise, timer should not be released. NSTimer is like every other NSObject, if you did not alloc, copy or retain, you do not need to release.

Resources