Reputation: 1154
I want to create an integration test which shows that a certain action results in the display of a modal view controller. The storyboard is setup with 2 viewcontrollers, one with a custom ViewController class the second with a default UIViewController class and title "second". The segue is set-up to be modal with identifier "modalsegue". Running the app in the simulator works brilliantly, but I am having a lot of trouble defining a correct test.
ViewController.m:
@implementation ViewController
- (IBAction)handleActionByPerformingModalSegue {
[self performSegueWithIdentifier:@"modalsegue" sender:self];
}
@end
Test:
- (void)testActionCausesDisplayOfSecondViewController {
ViewController * vc =
[[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil]
instantiateViewControllerWithIdentifier:@"ViewController"];
[vc handleActionByPerformingModalSegue];
STAssertEquals(vc.presentedViewController.title, @"second",
@"Title of presented view controller should be second but is %@",
vc.presentedViewController.title, nil);
}
Running the test results in the following output:
2013-06-23 17:38:44.164 SeguesRUs[15291:c07] Warning: Attempt to present <UIViewController: 0x7561370> on <ViewController: 0x7566590> whose view is not in the window hierarchy!
SeguesRUsTests.m:33: error: -[SeguesRUsTests testActionCausesDisplayOfSecondViewController] : '<00000000>' should be equal to '<9c210d07>': Title of presented view controller should be second but is (null)
What am I doing wrong? Is there an easy way to avoid the first message?
Upvotes: 5
Views: 1613
Reputation: 31486
As the error message points out, the problem is that you're trying to present on a UIViewController
whose view is not in the UIWindow
hierarchy.
The easiest way to fix it:
- (void)testExample {
//
// Arrange
// Storyboard
//
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
//
// Arrange
// View Controller
//
UIViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"ViewController"];
[UIApplication sharedApplication].keyWindow.rootViewController = viewController;
//
// Act
//
[viewController performSegueWithIdentifier:@"ModalSegue" sender:nil];
//
// Assert
//
XCTAssertEqualObjects(viewController.presentedViewController.title, @"Second");
}
Upvotes: 1
Reputation: 790
Here is what I do. Assume I have the DocumentsVC with manually triggered segue (DocumentsDetailVC) connected. Below is my setup and then I test for 1. the existence of the segue and then 2. I force the view controller (in my case I post a notification) to trigger its performSegueWithIdentifier and intercept the prepareForSegue Method to see if everything for the new view controller (DocumentsDetailVC) is set up. This includes method swizzling.
I have not that I use OCHamcrest/OCMockito for unit testing and that all my segues are named after the target view controller added by "Segue" ([self appDelegate] segueIdentifierForClass:[SomeClass class]]).
- (void)setUp
{
[super setUp];
_isPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
realPrepareForSegue = @selector(prepareForSegue:sender:);
testPrepareForSegue = @selector(documentsBrowserTest_prepareForSegue:sender:);
UIStoryboard *storyboard = nil;
if (_isPad) {
storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil];
}
else {
storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPad" bundle:nil];
}
UINavigationController *navController = [storyboard instantiateInitialViewController];
self.sut = (DocumentsBrowserVC *)navController.topViewController;
[self.sut view];
}
- (void)test_DocumentsDetailsVCSegueConnected
{
if (_isPad == FALSE) {
STAssertNoThrow([self.sut performSegueWithIdentifier:[[self appDelegate] segueIdentifierForClass:[DocumentsDetailVC class]] sender:self], @"DocumentsDetailVC should be connected");
}
}
- (void)test_providerDidSelectPathLevelObject_triggersDocumentsDetailsVCSegueSectionIdFile
{
[DocumentsBrowserTest swapInstanceMethodsForClass:[DocumentsBrowserVC class]
selector:realPrepareForSegue
andSelector:testPrepareForSegue];
[[NSNotificationCenter defaultCenter] addObserver:self.sut selector:@selector(providerDidSelectPathLevelObject:) name:ProviderDidSelectPathLevelObjectNotification object:nil];
// when
PathLevelObject *plo = self.pathLevelObjects[SectionIdFile][4];
NSDictionary *userInfo = @{OBJECT_KEY : plo , BROWSER_AREA_KEY : @(DocumentsFolder)};
[[NSNotificationCenter defaultCenter] postNotificationName:ProviderDidSelectPathLevelObjectNotification object:nil userInfo:userInfo];
// then
if (_isPad == FALSE) {
assertThat(NSStringFromClass([objc_getAssociatedObject(self.sut, storyboardSegueKey) class]), is(equalTo(@"UIStoryboardPushSegue")));
assertThatBool([[objc_getAssociatedObject(self.sut, storyboardSegueKey) destinationViewController] isKindOfClass:[DocumentsDetailVC class]], is(equalToBool(TRUE)));
assertThat(objc_getAssociatedObject(self.sut, senderKey), is(equalTo(self.sut)));
}
else {
assertThatInteger(self.sut.detailViewController.browsingArea, is(equalToInteger(DocumentsFolder)));
assertThat(self.sut.detailViewController.pathLevelObject, is(equalTo(plo)));
}
[[NSNotificationCenter defaultCenter] removeObserver:self.sut];
[DocumentsBrowserTest swapInstanceMethodsForClass:[DocumentsBrowserVC class]
selector:realPrepareForSegue
andSelector:testPrepareForSegue];
}
Upvotes: 0