1

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");
}

1 Answers1

1

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;
}
MoDJ
  • 4,309
  • 2
  • 30
  • 65
  • How do i use this logic with respect to my code ? :( – Pankaj Teckchandani Dec 06 '15 at 08:26
  • You just insert the method into a test case and call it after a NSTimer has been added to the event loop. Just start with calling waitFor until you understand how things work. The waitUntilTrue method makes it possible to return faster in the case where a known selector will return true as a result of some async condition. – MoDJ Dec 06 '15 at 21:23
  • Kool. Got it how its working. Thanks a ton :) – Pankaj Teckchandani Dec 07 '15 at 03:00