This line:
[window addSubview:nav.view];
does NOT add a view to the screen immediately. It is displayed by the OS in some future run loop on a possibly different thread. The actual implementation we can't be sure of.
This is why Apple defines delegate methods like viewDidAppear/viewWillAppear, otherwise we would not need them as we would know precisely when these events occur.
Moreover, adding a subview as you said, does indeed retains the view. It does NOT however retain the view controller or the navigation controller. Since the navigation controller WILL retain any added view controllers, we do not have to back them with an ivar.
But, your reference to the navigation controller must persist beyond the scope of the method. or depending on your code it could be dealloc-ed or have its reference lost.
So you must keep a reference to the navigation controller with an ivar and set it like so:
self.navigationController = nav;
So even though nav.view contains a pointer to testViewController.view, the application has no reference the navigation controller and, by extension, the view. The result is a blank screen.
To make this more obvious that it isn't a retain/release problem, you are actually leaking in the following method:
self.testViewController = [[TestViewController alloc] initWithNibName:@"TestView" bundle: [NSBundle mainBundle]];
You need to autorelease to balance out your retain/releases by:
self.testViewController = [[[TestViewController alloc] initWithNibName:@"TestView" bundle: [NSBundle mainBundle]] autorelease];
So, that means your view has never, ever been deallocated any time you have ran this code. Which further assures us that your issue is indeed a lost reference.