Testing NSWidowController using OCMock - iphone

I've been trying to come up with a a way to unit test my applicationDidFinishLaunching delegate using OCMock. My NSWindowController is instantiated here and I'd like to test it. Here's my test code:
id mockWindowController = [OCMockObject niceMockForClass:[URLTimerWindowController class]];
[[mockWindowController expect] showWindow:self];
NSUInteger preRetainCount = [mockWindowController retainCount];
[appDelegate applicationDidFinishLaunching:nil];
[mockWindowController verify];
When I run the test, I get the error:
"OCMockObject[URLTimerWindowController]: expected method was not invoked: showWindow:-[URLTimerAppDelegateTests testApplicationDidFinishLaunching]"
The log gives more detail:
"Test Case '-[URLTimerAppDelegateTests testApplicationDidFinishLaunching]' started.
2011-04-11 08:36:57.558 otest-x86_64[3868:903] -[URLTimerWindowController loadWindow]: failed to load window nib file 'TimerWindow'.
Unknown.m:0: error: -[URLTimerAppDelegateTests testApplicationDidFinishLaunching] : OCMockObject[URLTimerWindowController]: expected method was not invoked: showWindow:-[URLTimerAppDelegateTests testApplicationDidFinishLaunching]
Test Case '-[URLTimerAppDelegateTests testApplicationDidFinishLaunching]' failed (0.005 seconds).
"
So I see that the NIB fails to load. Ok, so how do I make it load while unit testing or somehow mock its load? I've already looked at the OCMock docs, Chris Hanson's tips on unit testing and a few other resources, including the WhereIsMyMac source code which behave in a similar fashion. My application for instantiating the window controller is this:
self.urlTimerWindowController = [[URLTimerWindowController alloc] init];
[self.urlTimerWindowController showWindow:self];
Any tips greatly appreciated.

The problem with your test is that mockWindowController and urlTimerWindowController are not the same object. And self in your test is not the same as self in the class under test. It doesn't really matter that the nib doesn't load in this case.
You generally can't mock an object when it's instantiated inside the method you want to test. One alternative is to instantiate the object in one method, then pass it to another method that finishes the setup. Then you can test the setup method. For example:
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.urlTimerWindowController = [[URLTimerWindowController alloc] init];
[self setUpTimerWindow:urlTimerWindowController];
}
-(void)setUpTimerWindow:(URLTimerWindowController *)controller {
[controller showWindow:self];
}
Then, you would test setUpTimerWindow::
-(void)testSetUpTimerWindowShouldShowWindow {
URLTimerAppDelegate *appDelegate = [[URLTimerAppDelegate alloc] init];
id mockWindowController = [OCMockObject niceMockForClass:[URLTimerWindowController class]];
[[mockWindowController expect] showWindow:appDelegate]; // this seems weird. does showWindow really take the app delegate as a parameter?
[appDelegate setUpTimerWindow:mockWindowController];
[mockWindowController verify];
[appDelegate release];
}

Related

Unit Testing for iOS

I am new to unit testing and am trying things out.
I created a view controller with 1 button (get sum), and 3 textfields (input 2 numbers and output the sum).
int aNum = [self.firstNumber.text intValue];
int bNum = [self.secondNumber.text intValue];
sum = aNum + bNum;
self.total.text = [NSString stringWithFormat:#"%i", sum];
[self dismissKeyboard];
And my testing codes:
vc = [[TestingViewController alloc] init];
vc.firstNumber.text = #"1";
vc.secondNumber.text = #"2";
[vc getSum:nil];
STAssertTrue([vc.total.text isEqualToString:#"3"], #"total should be 3");
The test failed because I have tried to work with UI elements.
My questions is: is it possible to test with UI elements like this? How would I write a test to achieve this?
Thanks guys!
Yes, testing UI element in unit tests is definitely possible.
Techniques for this and more (including testing code which relies on networks) is covered in Test-Driven iOS Development (Developer's Library) by Graham Lee is a great resource:
http://www.amazon.com/Test-Driven-iOS-Development-Developers-Library/dp/0321774183
Could be that the view isn't getting loaded. I had the same problem when I started with unit testing on iOS. Try calling [vc loadView]; after you init the viewcontroller.
vc = [[TestingViewController alloc] init];
[vc loadView];
You need to load the NIB which is done by accessing the view (see iOS' view lifecycle):
vc = [[TestingViewController alloc] init];
// vc.firstNumber is nil right now, because the view isn't loaded
[vc view];
// vc.firstNumber now exists
vc.firstNumber.text = #"1";
vc.secondNumber.text = #"2";
[vc getSum:nil];
STAssertTrue([vc.total.text isEqualToString:#"3"], #"total should be 3");
Alternatively, if the view is part of a UIWindow hierarchy, you don't need to do this (ie - end-to-end testing)
You're trying to test a feature of your application while it runs. Do you understand the difference between unit tests and application tests? Did you read and obey the documentation on application tests? Are you running on the device?
Here's a helpful tutorial: http://cocoawithlove.com/2009/12/sample-iphone-application-with-complete.html
to test the UI, u can use javascript and that runs in Instruments.

Can I build a Unit Test for an External Service (iPhone)?

My app registers with an external service. The service phones me up with a two-digit code that I have to enter in (first time only) in order to use the service. The rest of the calls to the service work fine afterwards.
How would I set up a unit test for a method that isn't complete until a response code is entered out of band?
Any ideas?
iOS SDK 4.2
Use OCMock to mock the service, that allows you to use stubbing and return the expected result.
For example:
Let's say this is your service:
#interface ServiceClass
- (NSString *) fetchData;
#end
This would be your mock service
id serviceMock = [[OCMockObject niceMockForClass:[ServiceClass class]];
[[[serviceMock stub] andreturn:#"56"] fetchData];
// we tell the mock to return the string "56" when the method fetchData is called
SomeViewController *mvc = [[SomeViewController alloc] init];
mvc.webService = serviceMock;
// Here we are injecting a mock into a view controller
You can use some Dependency injection techniques to inject this mock into your view controller
#synthesize webService = _webService;
- (IBAction)buttonClicked:(id)sender
{
NSString *result = [self.webService fetchData];
}
- (ServiceClass *)webService
{
if (!_webService)
{
_webService = [[ServiceClass alloc] init];
}
return _webService;
}

iPhone SDK NSInternalInconsistencyException

I'm hitting a wall over and over again, trying to solve a problem I've got in xcode. I'm a newbie and started coding just a while ago.
I'm trying to make a XML parser based on this tutorial: http://cocoadevblog.com/iphone-tutorial-creating-a-rss-feed-reader
which works fine separately, but when I'm implementing it into my own project, I get the 'NSInternalInconsistencyException' error, as a result of the following code:
----File: Parser.m----
- (void)parserDidEndDocument:(NSXMLParser *)parser {
if ([_delegate respondsToSelector:#selector(parsedInformation::)]){
[_delegate parsedInformation:information];
}else{
[NSException raise:NSInternalInconsistencyException
format:#"Delegate (%d) doesn't respond to parsedInformation:", _delegate];
}
}
I've tried to remove the if-phrase, and then it calls the correct function, but the data which is supposed to be overhanded, won't get through.
Project setup
The project is a tab-based application. I'm having three classes:
Parser
AlphaTab
RootDelegate
In RootDelegate I used the following code to initialize the tab-view, and then to initialiaze the AlphaTab as a tableView being part of a navigationView:
----RootDelegate.m ----
tabBarController = [[UITabBarController alloc] init];
alphaTab = [[AlphaTab alloc] initWithTabTitle:#"AlphaTab" navigationTitle:#"Exploring"];
UINavigationController *tableNavController = [[[UINavigationController alloc] initWithRootViewController:alphaTab] autorelease];
tableNavController.delegate = self;
[alphaTab release]; // creates your table view's navigation controller, then adds the created view controller. Note I then let go of the view controller as the navigation controller now holds onto it for me. This saves memory.
So good so far.. the problem comes when I use the Parser class, which parses a given XML file. This class is initialized and only implemented in the AlphaTab - therefore it has nothing to do with the RootDelegate class at all. The initialization is done as:
----File AlphaTab.m ----
- (void)loadData{
if(information==nil){
Parser *XMLParser = [[Parser alloc] init];
[XMLParser parseFeed:#"http://frederikbrinck.com/bodil/Example.xml" withDelegate:self];
[XMLParser release];
}else {
[self.tableView reloadData];
}
}
I'm suspecting the parameter withDelegate's value "self" to be the problem, which I think referres to the super class RootDelegate, but I'm not sure. Likewise, I don't know to pass the AlphaTab class' delegate to the function, which I think would solve the problem.
I'm ought to think, that the problem could be created from this line aswell:
----FILE: Parser.h ----
#protocol AlphaTab <UITableViewDelegate>
- (void)parsedInformation:(NSArray *)i;
#end
I've done some research about protocols and respondsToSelector, but honestly, I didn't understand much, since my code is seen from the programmatic perspective of view, without using the InterfaceBuilder at all, since I've been adviced to do that. It hasn't lead to the solution of the problem either.
For further understanding, I then want this function in AlphaTab.m to be called, when the information is parsed.
----FILE AlphaTab.m ----
- (void)parsedInformation:(NSArray *)i {
NSLog(#"The parser has completed parsing");
information = i;
NSLog(#"This is the information: %d", [[information objectAtIndex:0] objectForKey:#"tabTitle"]);
[self.tableView reloadData];
}
I've looked on the net, and I found some explications about the NSInternalInconsistencyException. I've tried to do them as well, for example by setting everybody with themselves as delegates. However, I had no luck. What wonders me most, is that when I use the Parser without having to subclass it's caller (this case: AlphaTab) to a main class, it works like a charm.
I hope you guys can give me a clue. If you need any more information please ask, and I'll be in disposition.
//Brinck10
Please see #warrenm and his comment.

Delegate confusion .. How do I find out when several delegates have finished their task?

I've built a basic Web XML to Core Data parsing system, but I am confused about how to set off several parsers at once, and know when they are all done.
This is my current setup, which gets just one parsed xml file ("news"). But I have several xml files I need to parse ("sport", "shop" etc). How would set all of these off, and know when they are all done?
// ViewController.m
DataGrabber *dataGrabber = [[DataGrabber alloc] init];
dataGrabber.delegate = self;
[dataGrabber getData:#"news"];
// DataGrabber delegate method (within ViewController) which gets called when dataGrabber has got all of the XML file
- (void) dataGrabberFinished:(DataGrabber *)dataGrabber
{
NSManagedObjectContext *context = [self managedObjectContext];
NSError *parseError = nil;
// Parser puts the xml into core data. Do I need delegate on this too?
Parser *xmlParse = [[Parser alloc] initWithContext:context];
[xmlParse parseXMLFileWithData:dataGrabber.payload parseError:&parseError];
[xmlParse release];
}
(this a follow-on from this question - Returning data from data-grabbing class from web? )
One option is to count how many you create and then have each one call back to a method that counts down from that total. When its back to zero they are all done.
You will need to set up a delegate for the parser just like you did for the downloader.

understanding TTNavigator

following situation:
in a TTTableViewController i added some Cells with URLs.
they are opening a class with #"tt://photos" for example. this works quite fine.
the first thing is, i saw some urls in TT Examples like #"tt/photos/1". is it possible to fetch this "1" in my photos class and say, for example okay, please open picture one, ore is this only another URL that was declared in TTNavigatior to open a specific Class?
the other thing is: is it possible to forward an object to the linked class?
clicking a cell opens #"tt://photos" (the linked class in my TTNavigator)
working with normal tableviews i can overwrite my init method and send an object with my initialize method, is this also possible by clicking my TTItems?
thanks!
figured it out myself, for those who need it:
First (passing "subURLs" in your navigator map)
navigating to an URL with #"tt://photos/firstphoto" is possible, you can fetch the "firstphoto" like this:
//Prepare your Navigator Map like this
[map from:#"tt://photos/(initWithNumber:)" toViewController:[PhotoVC class]];
In your PhotoVC you can access this Number:
-(void) initWithNumber: (NSString*)number {
NSLog(#"%#",number);
}
calling your View Controller with this url would look:
PhotoVC* controller = [[PhotoVC alloc] initWithNumber:#"1"];
[navigationController pushViewController:controller animated:YES];
[controller release];
Second (passing objects in an TTTableViewController)
its a bit tricky, but you dont have to Subclass anything.
first, nil the URL in the TableItem
[TTTableLink itemWithText:#"TTTableLink" URL:nil]
in your TTTableViewController write down this method
- (void)didSelectObject:(id)object atIndexPath:(NSIndexPath*)indexPath {
TTURLAction *urlAction = [[[TTURLAction alloc] initWithURLPath:#"tt://photos"] autorelease];
urlAction.query = [NSDictionary dictionaryWithObject:#"firstphoto" forKey:#"photo"];
urlAction.animated = YES;
[[TTNavigator navigator] openURLAction:urlAction];
}
now in your your PhotoVC you need something like this
- (id)initWithNavigatorURL:(NSURL*)URL query:(NSDictionary*)query {
if (self = [super init]) {
NSLog(#"%#",query);
}
return self;
}
and you are done ;)
I was trying to implement choise's answer, learned a lot, and eventually had to get the callouts showing up and keep the implementation with many urls simple, so here's what i did.
Keep URL in the TableItem,
Use this code in the TTTableViewController subclass.
- (void)didSelectObject:(id)object atIndexPath:(NSIndexPath*)indexPath {
NSLog(#"Its url is %#", [object URL]);
TTURLAction *urlAction = [[[TTURLAction alloc] initWithURLPath:(NSString *)[object URL]] autorelease];
urlAction.query = [NSDictionary dictionaryWithObject:self.user forKey:#"user"];
urlAction.animated = YES;
[[TTNavigator navigator] openURLAction:urlAction];
}
- (BOOL)shouldOpenURL:(NSString*)URL {
return NO;
}
That "shouldOpenURL:" was discovered looking through TTTableViewController, I tried it out, and it worked. Now the table view is not opening a duplicate view, and there are callouts!
Thanks choise!
Although choice's answer works for multiple params when u are creating the TTURLAction in code it is not very useful when u want to embed links to view controllers in your TTStyledLabel.One solution to that is to use multiple params in a single string.
<a href='app://view2/param1=value1&param2=value2&...'>LabelName</a>
if you want the code to parse such urls and get the params please feel free to send me a message and I will send you my parser classes.
(or you can build your own with NSScanner!)
Also dont forget to escape the &s otherwise TTStyledLabel would not like it!
You don't need to run this on current version 1.0.6.2 for TTTableViewController. The "URL" option is working as expected. If it's not working for you, then your URL is broken or your are calling the wrong function on your ViewController. The function you have to call through URL must return an id (be a constructor for a ViewController) of a ViewController. Then it'll work as expected.
I'll changed the example form choise to be like TTNavigator expect it to be.
Add a mapping, which TTNavigator will use to navigate:
//Prepare your Navigator Map like this
[map from:#"tt://photos/(initWithNumber:)" toViewController:[PhotoVC class]];
Create a TTTableLink (or TTStyledText, or other) with an URL set, which should mach your map:
[TTTableLink itemWithText:#"TTTableLink" URL:#"tt://photos/1"]
Add this to your PhotoVC to be called by TTNavigator on the given URL
-(id) initWithNumber: (NSString*)number {
if (self = [super init]) {
self.title = #"Some Title";
NSLog(#"%#",number);
}
return self;
}
You don't need to overwrite the function didSelectObject, as the TTNavigator will call your ViewController through defined constructor function tt://photos/(initWithNumber:)