1

I'm writing a unit test and I find popViewControllerAnimated:YES doesn't work.

(void)testNavi {
  UINavigationController *navi = [[UINavigationController alloc] init];
  UIViewController *controllerA = [[UIViewController alloc] initWithNibName:nil bundle:nil];
  UIViewController *controllerB = [[UIViewController alloc] initWithNibName:nil bundle:nil];
  [navi pushViewController:controllerA animated:NO];
  [navi pushViewController:controllerB animated:NO];
  [navi popViewControllerAnimated:YES];
  XCTAssertEqual(navi.topViewController, controllerA);
}

If I change [navi popViewControllerAnimated:YES] into [navi popViewControllerAnimated:NO], it works. I don't know why.

Gonghan
  • 287
  • 2
  • 3
  • 10
  • 1
    You can't push a view controller while pushing a view controller. When it's not animated, there's no time requirement for the push animation and the push of A has finished before you push B. Otherwise, weirdness happens and controllerB probably doesn't get pushed. Then, when you pop, you're really popping A off the stack. – Logan Nov 13 '14 at 21:24

3 Answers3

0

You'd better to call popViewControllerAnimated after view was created. So you may make subclass of NavigationController and implements it like this.

- (void)init {
   self = [super init];
   if (self) {
      UIViewController *controllerA = [[UIViewController alloc] initWithNibName:nil bundle:nil];
      UIViewController *controllerB = [[UIViewController alloc] initWithNibName:nil bundle:nil];
      self.viewControllers = @[controllerA, controllerB];
   }
   return self;
}

- (void)viewWillAppear {
   [self popViewControllerAnimated:YES];
}
Robasan
  • 155
  • 10
0

This is because the animation happens asynchronously on another thread. The animation to pop the view controller takes a small amount of time, but the test assertion is made before the animation has completed, so the topViewController has not changed yet.

The best way to test this it to mock the UINavigationController and verify that popViewController:YES is called.

AnthonyMDev
  • 1,496
  • 1
  • 13
  • 27
0

I had a similar problem testing pushViewController when the push is animated.

I was able to solve this by wrapping my assertion in this handy delay function from matt.

When introducing a delay in your test, it is necessary to use expectations to ensure the assertions are checked. Otherwise, the test completes before the delay code executes, resulting in a passed test that did not check your assertions!

// code to push the view controller goes here...

// set up the expectation
let expectation = expectationWithDescription("anything you want")

// now, assert something about the navigation controller 
// after waiting 1/10th second for the animation to finish
delay (0.1) {

    // for example: Navigation controller should have the 
    // expected number of view controllers
    XCTAssertEqual(1, navigationController.viewControllers.count)

    // if we get this far, then assertions passed and we must
    // fulfill expectations to pass the test
    expectation.fulfill()
}

// wait longer than the delay value, so the code in the delay closure
// has time to finish
waitForExpectationsWithTimeout(0.2, handler: nil)
Community
  • 1
  • 1
Mike Taverne
  • 9,156
  • 2
  • 42
  • 58