+(NSString *) classMethod:(id)someDataObject
{
NSString *returnStr;
//do something with someDataObject and set returnStr
//...
returnStr = [NSString stringWithFormat:#"%#%#", returnStr,[self getCurrentTimestamp]];
return returnStr;
}
+ (NSString *)getCurrentTimestamp
{
NSNumber *t = [NSNumber numberWithLong:[[NSDate date] timeIntervalSince1970]];
return [t stringValue];
}
I am struggling to write test case for the classMethod because when I run OCUnit test and pass OCMock object to classMethod the output will be always different as I add timestamp. What is the best approach to stub class method - getCurrentTimestamp? Is it actually possible to stub class methods of the class that is being tested?
I tried like this, but it is not working:
id mock = [OCMockObject mockForClass:[MyClass class]];
[[[mock stub] andReturn:#1378468455] getCurrentTimestamp];
NSString *str = [MyClass classMethod:someMockObject];
This works fine for me:
+(NSString *) classMethod:(id)someDataObject
{
NSString *returnStr = #"prefix-";
returnStr = [NSString stringWithFormat:#"%#%#", returnStr,[self getCurrentTimestamp]];
return returnStr;
}
+ (NSString *)getCurrentTimestamp
{
NSNumber *t = [NSNumber numberWithLong:[[NSDate date] timeIntervalSince1970]];
return [t stringValue];
}
- (void)testClass
{
id mock = [OCMockObject mockForClass:[Bar class]];
[[[mock stub] andReturn:#"1378468455"] getCurrentTimestamp];
NSString *str = [Bar classMethod:nil];
NSLog(#"Str: %#", str);
}
// Result:
// Str: prefix-1378468455
Can you confirm your OCMock version, change the number to a string, and / or explain how it is not working for you?
Thank you #BenFlynn. Your example (as well as mine) is working indeed. The problem was that "MyClass.h" was checked in Target Membership for the unite test too. "MyClass.h" should target only project (I am not sure why, maybe someone can explain it).
Related
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
'isMemberOfClass' returning 'NO' when custom init
I've some trouble with the "isMemberOfClass"-Method.
I have a class, that generates and returns objects ("MyObject")
// ObjectFactory.h
...
-(MyObject*)generateMyObject;
...
// ObjectFactory.m
...
-(MyObject*)generateMyObject
{
MyObject *obj = [[MyObject alloc]init];
obj.name = #"Whatever"; // set properties of object
return obj;
}
...
And there's a unittest-class, that calls the generateMyObject-selector and checks the class of the returned object:
...
ObjectFactory *factory = [[ObjectFactory alloc]init];
MyObject *obj = [factory generateMyObject];
if (![obj isMemeberOfclass:[MyObject class]])
STFail(#"Upps, object of wrong class returned...");
else
...
I expect, that the else-part is processed...but the STFail(...) is called instead, but why?
Thx for any help!
Regards,
matrau
Ok, here is the original copy&pasted code:
//testcase
- (void)test001_setCostumeFirstCostume
{
NSString *xmlString = #"<Bricks.SetCostumeBrick><costumeData reference=\"../../../../../costumeDataList/Common.CostumeData\"/><sprite reference=\"../../../../..\"/></Bricks.SetCostumeBrick>";
NSError *error;
NSData *xmlData = [xmlString dataUsingEncoding:NSASCIIStringEncoding];
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData
options:0 error:&error];
SetCostumeBrick *newBrick = [self.parser loadSetCostumeBrick:doc.rootElement];
if (![newBrick isMemberOfClass:[SetCostumeBrick class]])
STFail(#"Wrong class-member");
}
// "MyObject"
#implementation SetCostumeBrick
#synthesize indexOfCostumeInArray = _indexOfCostumeInArray;
- (void)performOnSprite:(Sprite *)sprite fromScript:(Script*)script
{
NSLog(#"Performing: %#", self.description);
[sprite performSelectorOnMainThread:#selector(changeCostume:) withObject:self.indexOfCostumeInArray waitUntilDone:true];
}
- (NSString*)description
{
return [NSString stringWithFormat:#"SetCostumeBrick (CostumeIndex: %d)", self.indexOfCostumeInArray.intValue];
}
#end
// superclass of SetCostumeBrick
#implementation Brick
- (NSString*)description
{
return #"Brick (NO SPECIFIC DESCRIPTION GIVEN! OVERRIDE THE DESCRIPTION METHOD!";
}
//abstract method (!!!)
- (void)performOnSprite:(Sprite *)sprite fromScript:(Script*)script
{
#throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:#"You must override %# in a subclass", NSStringFromSelector(_cmd)]
userInfo:nil];
}
#end
// the "factory" (a xml-parser)
- (SetCostumeBrick*)loadSetCostumeBrick:(GDataXMLElement*)gDataSetCostumeBrick
{
SetCostumeBrick *ret = [[SetCostumeBrick alloc] init];
NSArray *references = [gDataSetCostumeBrick elementsForName:#"costumeData"];
GDataXMLNode *temp = [(GDataXMLElement*)[references objectAtIndex:0]attributeForName:#"reference"];
NSString *referencePath = temp.stringValue;
if ([referencePath length] > 2)
{
if([referencePath hasSuffix:#"]"]) //index found
{
NSString *indexString = [referencePath substringWithRange:NSMakeRange([referencePath length]-2, 1)];
ret.indexOfCostumeInArray = [NSNumber numberWithInt:indexString.intValue-1];
}
else
{
ret.indexOfCostumeInArray = [NSNumber numberWithInt:0];
}
}
else
{
ret.indexOfCostumeInArray = nil;
#throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:#"Parser error! (#1)"]
userInfo:nil];
}
NSLog(#"Index: %#, Reference: %#", ret.indexOfCostumeInArray, [references objectAtIndex:0]);
return ret;
}
SOLUTION:
Eiko/jrturton gave me a link to the solution - thx: isMemberOfClass returns no when ViewController is instantiated from UIStoryboard
The problem was, that the classes were included in both targets (app and test bundle)
Thank you guys for your help :)
You generally want isKindOfClass:, not isMemberOfClass. The isKindOfClass: will return YES if the receiver is a member of a subclass of the class in question, whereas isMemberOfClass: will return NO in the same case.
if ([obj isKindOfClass:[MyObject class]])
For example,
NSArray *array = [NSArray array];
Here [array isMemberOfClass:[NSArray class]] will return NO but [array isKindOfClass:[NSArray class]] will return YES.
Ok, with different class addresses per your comment, I think I can track this down to be a duplicate of this:
isMemberOfClass returns no when ViewController is instantiated from UIStoryboard
Basically, your class is included twice.
I'm still trying to wrap my head around how things should be done in the object-oriented world and I think my problem is that I don't understand how to best utilize encapsulation. Specifically, I have lots of small bits of code that I use in several classes in my project. For example:
+ (NSString *)getFormattedDate;
+ (NSString *)getResultsFilePath;
+ (NSError *)removeFileFromCache:(NSString *)fileName;
These are all 3-5 line methods that I use in more than one class. My standard practice has been to put these snippets into a Utility.inc file and call them when I need them. Is that appropriate in the object-oriented world or should each class be self-contained? And if it's appropriate, would you put the code into a singleton or just a regular class file and [[Utilities alloc] init] in each class where you want to use the methods?
Look into using Categories. For the examples you gave, these are methods related to objects of a particular class that happen to be used in several of your own classes. Categories will allow you to park these often used methods where they can be associated with the common factors.
Create a utitity singelton which will be created only ones and then used by the other classes.
Thanks for the answers. I'm not sure that this the right way to do things, but this is what I've done on the projects I just submitted.
I made two classes, one for Utility methods and one for globals. The methods in the Utilities class are all class methods since they operate on files and constants or globals. Then I made a singleton for global variables. I have all of my global constants in the .pch file. Also in the .pch file I put the following two lines of code so that the utilities and globals are available everywhere.
// File for utilities like file delete, find Documents file
#import "Utilities.h"
#import "Globals.h"
Accessing the methods is straightforward. Here's an example of a call to both methods to generate an HTML header for an email.
NSString *gameNameHeader = [NSString stringWithFormat:#"<p> </p><h1>%# Results</h1><h2>%#%#</h2>",GAME_NAME_TITLE,[Utilities formattedClientName], [Utilities formattedDate]];
In case anyone can use it, here is my current version of the code. (Sorry for the formatting-I can't seem to get the wiki to cooperate.)
#interface Utilities : NSObject {
}
+ (NSString *)formattedDate;
+ (NSString *)formattedClientName;
+ (NSString *)cachedResultsFilePath;
+ (NSString *)cachedResultsFileContents;
+ (NSString *)resultsFileName;
+ (NSError *)removeFileFromCache:(NSString *)fileName;
+ (NSString *)applicationCachesDirectory;
+ (NSString *)applicationDocumentsDirectory;
+ (NSString *)applicationLibraryDirectory;
+ (NSError *)copyCachedResultsToFile;
#end
#import "Utilities.h"
#implementation Utilities {
}
+ (NSString *)formattedDate {
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd"];
NSString *todaysDate = [dateFormatter stringFromDate:[NSDate date]];
return todaysDate;
}
+ (NSString *)formattedClientName {
NSString *client = [NSString stringWithFormat:#" "];
if( [Globals sharedInstance].currentClient ) client = [NSString stringWithFormat:#" %# ",[Globals sharedInstance].currentClient];
return client;
}
+ (NSString *)cachedResultsFilePath {
NSString *resultsFilePath = [[self applicationCachesDirectory] stringByAppendingPathComponent:#"Results.txt"];
return resultsFilePath;
}
+ (NSString *)cachedResultsFileContents {
NSStringEncoding encoding; NSError* error = nil;
NSString *resultsText = [NSString stringWithContentsOfFile:[self cachedResultsFilePath] usedEncoding:&encoding error:&error];
return resultsText;
}
+ (NSString *)resultsFileName {
return [NSString stringWithFormat:#"%# Results%#%#.html",GAME_NAME_TITLE,[self formattedClientName],[self formattedDate] ];
}
+ (NSError *)removeFileFromCache:(NSString *)fileName {
NSError *error = nil;
NSFileManager *localFileManager=[[NSFileManager alloc] init];
NSString *fullPath = [NSString stringWithFormat:#"%#/%#", [self applicationCachesDirectory],fileName];
[localFileManager removeItemAtPath: fullPath error:&error ];
return error;
}
+ (NSString *)applicationCachesDirectory {
return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSString *)applicationLibraryDirectory {
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSError *)copyCachedResultsToFile {
// Grab the header and footer and put it around the cached data
NSStringEncoding encoding; NSError *error = nil;
NSString *htmlHeaderTextPath = [[NSBundle mainBundle] pathForResource:#"HTML_header" ofType:#"html" ];
NSString *htmlHeaderText = [NSString stringWithContentsOfFile:htmlHeaderTextPath usedEncoding:&encoding error:&error];
NSString *cachedResultsText = [NSString stringWithContentsOfFile:[self cachedResultsFilePath] usedEncoding:&encoding error:&error];
// Write the results to a file if there are any
if (cachedResultsText) {
NSString *htmlFooterTextPath = [[NSBundle mainBundle] pathForResource:#"HTML_footer" ofType:#"html" ];
NSString *htmlFooterText = [NSString stringWithContentsOfFile:htmlFooterTextPath usedEncoding:&encoding error:&error];
NSString *gameNameHeader = [NSString stringWithFormat:#"<h1>%# Results for%#%#</h1>",GAME_NAME_TITLE,[self formattedClientName],[self formattedDate] ];
NSString *tempStringP1 = [htmlHeaderText stringByAppendingString:gameNameHeader];
NSString *tempStringP2 = [tempStringP1 stringByAppendingString:cachedResultsText];
NSString *formattedTextForPrinting = [tempStringP2 stringByAppendingString:htmlFooterText];
NSString *resultsFilePath = [ [Utilities applicationDocumentsDirectory] stringByAppendingPathComponent:[self resultsFileName] ];
if ( !([[NSFileManager defaultManager] fileExistsAtPath:resultsFilePath]) ) {
if (! ([[NSFileManager defaultManager] createFileAtPath:resultsFilePath contents:nil attributes:nil]) ) {
NSLog(#"Error was code: %d - message: %s", errno, strerror(errno));
}
}
NSFileHandle *fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:resultsFilePath];
[fileHandler writeData:[formattedTextForPrinting dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandler closeFile];
}
return error;
}
#end
Globals in a singleton. Probably not thread-safe, but I don't care right now.
#interface Globals : NSObject {
}
#property (nonatomic, strong) NSString *currentClient;
#property (nonatomic, strong) NSString *showmePict;
#property BOOL checkBoxes;
+ (Globals *)sharedInstance;
- (void)resetClient;
#end
#implementation Globals {
}
static Globals *singleton = nil;
#synthesize currentClient = _currentClient;
#synthesize showmePict = _showmePict;
#synthesize checkBoxes = _checkBoxes;
+(Globals *) sharedInstance {
NSLog (#"sharedInstance of Globals called.");
if (nil != singleton) return singleton;
static dispatch_once_t pred; // lock
dispatch_once(&pred, ^{ // this code is at most once
singleton = [[Globals alloc] init];
});
return singleton;
}
- (void)resetClient {
self.currentClient = nil;
}
#end
I am using GHUnit & OCMock to do some testing work in my iOS app.
So I have some trouble integrating them.
The following code works well.
NSString *s = [NSString stringWithString:#"122"];
id mock = [OCMockObject partialMockForObject:s];
[[[mock stub] andReturn:#"255"] capitalizedString];
NSString *returnValue = [mock capitalizedString];
GHAssertEqualObjects(returnValue, #"255", #"Should be equal");
[mock verify];
But when I change [[[mock stub] andReturn:#"255"] capitalizedString]; into
[[[mock stub] andDo:^(NSInvocation *invocation) {
[invocation setReturnValue:#"255"];
}] capitalizedString];
I got an error which says "Reason: 'NSCFString' should be equal to '255'. Should be equal"
I think the two statements should do exactly the same thing. Am I wrong?
setReturnValue: expects a pointer to the return value, so your block should be:
void (^theBlock)(NSInvocation *) = ^(NSInvocation *invocation) {
NSString *capitalizedString = #"255";
[invocation setReturnValue:&capitalizedString];
};
In ruby, I can .inspect from an object to know the details. How can I do the similar thing in objective c? Thank you.
If you just want something to print you can use description as said before.
I'm not a Ruby guy myself, but if I understand this correctly .inspect in Ruby prints all the instance variables of an object. This is not something built into Cocoa. If you need this you can use the runtime system to query this information.
Here is a quick category I put together which does that:
#import <objc/objc-class.h>
#interface NSObject (InspectAsInRuby)
- (NSString *) inspect;
#end
#implementation NSObject (InspectAsInRuby)
- (NSString *) inspect;
{
NSMutableString *result = [NSMutableString stringWithFormat: #"<%#:%p", NSStringFromClass( [self class] ), self ];
unsigned ivarCount = 0;
Ivar *ivarList = class_copyIvarList( [self class], &ivarCount );
for (unsigned i = 0; i < ivarCount; i++) {
NSString *varName = [NSString stringWithUTF8String: ivar_getName( ivarList[i] )];
[result appendFormat: #" %#=%#", varName, [self valueForKey: varName]];
}
[result appendString: #">"];
free( ivarList );
return result;
}
#end
-[NSObject description] provides a basic description of an object (similar to toString in Java--I don't really know about .inspect in Ruby). description is automatically called in when you print an object in NSLog (e.g. NSLog(#"#%", myObject)).
For other introspection methods, I'd suggest looking at the NSObject reference. There are also a lot of things you can do directly with the Objective-C runtime.
Just print it out with NSLog
NSLog(#"%#", myObject);
It will automatically call the object's description method. If this is a class you created, you will want to define that (return an NSString with the info).
Take a look at this question.
The description method of NSObject is similar to inspect
In your NSObject's h file write this :
(NSDictionary *)dictionaryRepresentation;
In your NSObject's m file write this :
(NSDictionary *)dictionaryRepresentation {
unsigned int count = 0;
// Get a list of all properties in the class.
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:count];
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
NSString *value = [self valueForKey:key];
// Only add to the NSDictionary if it's not nil.
if (value)
[dictionary setObject:value forKey:key];
}
free(properties);
return dictionary; }
(NSString *)description {
return [NSString stringWithFormat:#"%#", [self dictionaryRepresentation]]; }
I have written an Objective-C framework which builds some HTML code with NSMutableString which returns the value as an NSString.
I have declared an NSString and NSMutableString in the inteface .h file:
NSString *_outputLanguage; // Tests language output
NSMutableString *outputBuilder;
NSString *output;
This is a sample from the framework implementation .m code (I cannot print the actual code as it is proprietary):
-(NSString*)doThis:(NSString*)aString num:(int)aNumber {
if ([outputBuilder length] != 0) {
[outputBuilder setString:#""];
}
if ([_outputLanguage isEqualToString:#"html"]) {
[outputBuilder appendString:#"Some Text..."];
[outputBuilder appendString:aString];
[outputBuilder appendString:[NSString stringWithFormat:#"%d", aNumber]];
}
else if ([_outputLanguage isEqualToString:#"xml"]) {
[outputBuilder appendString:#"Etc..."];
}
else {
[outputBuilder appendString:#""];
}
output = outputBuilder;
return output;
}
When I wrote a text program, NSLog simply printed out "(null)". The code I wrote there was:
TheClass *instance = [[TheClass alloc] init];
NSString *testString = [instance doThis:#"This String" num:20];
NSLog(#"%#", testString);
[instance release];
I hope this is enough information!
I'm guessing that you're forgetting to alloc/init your strings...
Make sure outputBuilder is valid. Where are you alloc/init'ing it?
Your doThis: method doesn't seem to initialise outputBuilder. So if it is a null pointer, then nothing will be done to it.