how to test object equality using xcttest? - objective-c

My method returns a NSNumber* and I want to unit test this method. Since my actual return value is NSNumber*, I create a new expected value of NSNumber*, but it fails. Here is the code:
NSNumber *cRating = [movie getRating:ratingDictionary ratingType:criticRating];
XCTAssertEqualObjects(cRating, [[NSNumber alloc]initWithInt:70], #"");
The error is:
[SFModelTest testGetCriticRatingMethod] failed: ((cRating) equal to ([[NSNumber alloc]initWithInt:70])) failed: ("70") is not equal to ("70")
Since it is saying "70" is not equal to "70", I am guessing it has to do with alloc init. Some pointer stuff that is not equal. Can somebody please help? Thank you.
Edit for comment: adding getRating method
- (NSNumber *)getRating:(NSDictionary *)movieDic ratingType:(enum RatingsEnum) rating{
NSNumber *result = 0;
NSNumber *ratingNum = 0;
switch (rating) {
case userRating:
{
ratingNum = [movieDic objectForKey:#"audience_score"];
break;
}
case criticRating:
{
ratingNum = [movieDic objectForKey:#"critics_score"];
break;
}
default:
break;
}
if(ratingNum && ratingNum > 0)
{
result = ratingNum;
}
return result;
}
The method returns NSNumber. And my test is:
NSNumber *cRating = [movie getRating:ratingDictionary ratingType:criticRating];
XCTAssertEqualObjects(cRating, [[NSNumber alloc]initWithInt:70], #"");
When I do a class NSLOG, it returns __NSCFConstantString. I am confused now.

I think that you're doing something wrong because XCTAssertEqualObjects(#(1), #(1), #"Not equal."); or XCTAssertEqualObjects([NSNumber numberWithInt:1], [NSNumber numberWithInt:1], #"Not equal."); if you are not familiar with literals, will pass.
You should checkout the getRating:ratingType: method to see which type of object it returns.
Try adding a breakpoint just before that XCTAssertEqualObjects and inspect the cRating instance.

Related

Objective-C:How to Converting int to id for selector's object?

I received a return value from SQLite fetch
int primaryKey = sqlite3_column_int(statement, 0);
and I'm going to use it as a selector's object:
[[ABC alloc] performSelector:#selector(abcWithAAA:) withObject:[NSNumber numberWithInt:primaryKey]];
the NSLog result for primarykey is a number 4:
NSLog(#"primaryKey:%i",primaryKey);
4
but the NSLog result for [NSNumber numberWithInt:primaryKey] is 131628896.
why? and how do i convert the int value correctly?
Thanks!
I solved the problem using an adapter method that does the cast for the withObject method.
My problem was that I wanted to use a typedef enum and pass it as value to the withObject.
I wanted to call this method using the performSelect message:
-(void) requestInfosAndPersistByMonsterType:(MonsterTypes)monsterType {
}
As you see it request a MonsterTypes typedef defined like this:
typedef enum
{
MonsterTypeIWerwolf = 0,
MonsterTypeITempler = 1,
MonsterTypeIUndefined,
} MonsterTypes;
Actually to be able to call the method above I build this adapter that calls it then:
-(void)monsterTypeFromObject:(id)_monsterType {
if ([_monsterType respondsToSelector:#selector(intValue)]) {
int _t = [_monsterType intValue];
switch (_t) {
case MonsterTypeIWerwolf:
_t = MonsterTypeIWerwolf;
break;
case MonsterTypeITempler:
_t = MonsterTypeITempler;
break;
default:
_t = MonsterTypeIUndefined;
break;
}
[self requestInfosAndPersistByMonsterType:_t];
}
}
It is used this way:
[self performSelector:#selector(monsterTypeFromObject:) withObject:[NSNumber numberWithUnsignedInt:monsterType] afterDelay:5.0f];
You can find it explained in more detail here:
http://kerkermeister.net/objective-c-adapter-from-nsinteger-to-id-when-using-performselector-withobject/
When you log [NSNumber numberWithInt:primaryKey], you're logging the address of an NSNumber object. If you want to see what's inside it should be [[NSNumber numberWithInt:primaryKey] intValue].
In other words, there's nothing in that to suggest your conversion is a problem.
[NSNumber numberWithInt:primaryKey] is object.
use %# for objects.
NSLog(#"%#", [NSNumber numberWithInt:primaryKey]);
131628896 is the memory address of the NSNumber object.
use:
- (void)abcWithAAA: (NSNumber *)number {
int primaryKey = [number intValue];
NSLog("%i", primaryKey);
}

Variadic function without nil termination

I am trying to create a method like the following:
- (void)setCondition:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
But since I'm not great with preprocessor, I hit an issue that I have fixed in the following code snippet, but I'd like to know if there's not cleaner way to achieve what I want which is to stop after the provided arguments
+ (CRCondition *)conditionWithFormat:(NSString *)format,... {
CRCondition *condition = [[CRCondition alloc] init];
NSArray *conditionSliced = [condition sliceFormatOperationFromString:format];
condition->_leftOperand = [[conditionSliced objectAtIndex:0] retain];
condition->_operator = [condition operatorFromString:[conditionSliced objectAtIndex:1]];
condition->_rightOperand = [[conditionSliced objectAtIndex:2] retain];
id eachObject;
va_list argumentList;
va_start(argumentList, format);
while ((eachObject = va_arg(argumentList, id))) {
if ([condition->_leftOperand isEqualToString:#"%K"]) {
[condition->_leftOperand release];
if ([eachObject isKindOfClass:[NSString class]])
condition->_leftOperand = [eachObject retain];
else
condition->_leftOperand = [[eachObject description] retain];
}
else if ([condition->_rightOperand isKindOfClass:[NSString class]] &&
[condition->_rightOperand isEqualToString:#"%#"]) {
[condition->_rightOperand release];
condition->_rightOperand = [eachObject retain];
}
else
break;
}
va_end(argumentList);
if (![condition isOperatorValid]) {
NSException *exception = [NSException exceptionWithName:#"Invalid Condition Operator"
reason:#"The operator passed is invalid. Must follow the following regex pattern: ([(=><)|(A-Z)]{1,2})"
userInfo:nil];
[exception raise];
}
return [condition autorelease];
}
The problem is with the while loop that circles and go past the provided arguments (I'm aware of why it's providing me other value, cmd args and such)
If you need any more explanation please add comments so I can get back to you.
The usual approach would be to parse the format string first and figure out how many arguments should follow it based on that (there is usually exactly one valid number of arguments for any format string). If the number of arguments is not deducible from a format string, it is common to terminate the list with nil (as in arrayWithObjects:...).

Add a (double) variable to NSMutableArray

I have a user input variable and i want to add that to an array. The firstStore is a BOOL type t determine if the array has been initialize. So the first time the STORE was called, it would initialize the array. I tried to make num equal to the operand (which is a double) by masking it NSNumber but that doesn't seem to work because i have this error "NSNumber may not respond to +operand", also the program crashes when it hits the line [memArray addObject:num]. I'm new at this stuff so any help would be greatly appreciated.
else if ([operation isEqual:#"Store"]) {
if(!firstStore){
memArray = [[NSMutableArray alloc] init];
NSNumber *num = [NSNumber operand];
[memArray addObject:num];
firstStore = YES;
} else {
//NSNumber *num = [NSNumber operand];
//[memArray addObject:num];
}
}
Try changing [NSNumber operand] to [NSNumber numberWithDouble:operand].
"NSNumber may not respond to +operand" means what is says: There is no such class-method operand. What was it supposed to do?
You dont need a bool to track if memArray is initialized. use:
if(!memArray)
or
if (memArray == nil)

get type of NSNumber

I want to get the type of NSNumber instance.
I found out on http://www.cocoadev.com/index.pl?NSNumber this:
NSNumber *myNum = [[NSNumber alloc] initWithBool:TRUE];
if ([[myNum className] isEqualToString:#"NSCFNumber"]) {
// process NSNumber as integer
} else if ([[myNum className] isEqualToString:#"NSCFBoolean"]) {
// process NSNumber as boolean
}
Ok, but this doesn't work, the [myNum className] isn't recognized by the compiler.
I'm compiling for iPhone.
I recommend using the -[NSNumber objCType] method.
It allows you to do:
NSNumber * n = [NSNumber numberWithBool:YES];
if (strcmp([n objCType], #encode(BOOL)) == 0) {
NSLog(#"this is a bool");
} else if (strcmp([n objCType], #encode(int)) == 0) {
NSLog(#"this is an int");
}
For more information on type encodings, check out the Objective-C Runtime Reference.
You can get the type this way, no string comparisons needed:
CFNumberType numberType = CFNumberGetType((CFNumberRef)someNSNumber);
numberType will then be one of:
enum CFNumberType {
kCFNumberSInt8Type = 1,
kCFNumberSInt16Type = 2,
kCFNumberSInt32Type = 3,
kCFNumberSInt64Type = 4,
kCFNumberFloat32Type = 5,
kCFNumberFloat64Type = 6,
kCFNumberCharType = 7,
kCFNumberShortType = 8,
kCFNumberIntType = 9,
kCFNumberLongType = 10,
kCFNumberLongLongType = 11,
kCFNumberFloatType = 12,
kCFNumberDoubleType = 13,
kCFNumberCFIndexType = 14,
kCFNumberNSIntegerType = 15,
kCFNumberCGFloatType = 16,
kCFNumberMaxType = 16
};
typedef enum CFNumberType CFNumberType;
If all you want is to differentiate between booleans and anything else, you can make use of the fact that boolean NSNumbers always return a shared instance:
NSNumber *num = ...;
if (num == (void*)kCFBooleanFalse || num == (void*)kCFBooleanTrue) {
// num is boolean
} else {
// num is not boolean
}
NSNumber explicitly doesn't guarantee that the returned type will match the method used to create it, so doing this at all is probably a bad idea.
However, you could probably do something like this (you could also compare to objc_getClass("NSCFNumber") etc., but this is arguably more portable):
Class boolClass = [[NSNumber numberWithBool:YES] class];
/* ... */
if([myNum isKindOfClass:boolClass]) {
/* ... */
}
In Swift:
let numberType = CFNumberGetType(answer)
switch numberType {
case .charType:
//Bool
case .sInt8Type, .sInt16Type, .sInt32Type, .sInt64Type, .shortType, .intType, .longType, .longLongType, .cfIndexType, .nsIntegerType:
//Int
case .float32Type, .float64Type, .floatType, .doubleType, .cgFloatType:
//Double
}
Use the method -[NSNumber objCType] method to get the type.
If the type's equal to #encode(BOOL), or the number itself is kCFBooleanFalse, or kCFBooleanTrue, it's a boolean.
If it's anything else but 'c', it's a number.
If it's 'c', what appears to be the only way supported way, without checking against private class names, or comparing against undocumented singletons, is to turn make an array of one element, the number, and then use NSJSONSerialization to get the string representation. Finally, check if the string representation contains the string "true" or "false". Here is the full code for checking if an NSNumber is a BOOL:
-(BOOL)isBool
{
if(!strcmp(self.objCType, #encode(BOOL)) ||
self == (void*)kCFBooleanFalse ||
self == (void*)kCFBooleanTrue)
{
return YES;
}
if(strcmp(self.objCType, "c"))
{
return NO;
}
NSString * asString = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:#[self] options:kNilOptions error:nil] encoding:NSUTF8StringEncoding];
return [asString containsString:#"true"] || [asString containsString:#"false"];
}
Note that using NSJSONSerialization is slow and if #NO/#YES ever stops always equalling kCFBooleanFalse/kCFBooleanTrue, then this method probably shouldn't be used in a tight loop.
The reason the compiler warns you and it doesn't work is because -[NSObject className] is declared in a category on NSObject on Mac OS X (in NSScriptClassDescription.h) and not declared on iPhone. (It doesn't support AppleScript, obviously.) NSStringFromClass([myNum class]) is what you should use to be safe across all platforms. Odds are that -className is declared as a simple wrapper around NSStringFromClass() anyway...
NSString *classString = NSStringFromClass([myNum class]);
That should ger the string you want.
To check that NSNumber contains a bool value Try this:
if (strcmp([myNumber objCType], [#(YES) objCType]) == 0)
NSLog(#"%#", [myNumber boolValue] ? #"true" : #"false");
objCType documentation states that The returned type does not necessarily match the method the number object was created with
Secondly, other methods of comparing the class of number to a given class type or assuming boolean number instances to be shared singletons are not documented behaviour.
A more(not completely though) reliable way is to depend on NSJSONSerialisation as it correctly recognises number instances created with bool and outputs true/false in json. This is something we can expect Apple to take care of while moving with new SDKs and on different architectures. Below is the code:
+(BOOL) isBoolType:(NSNumber*) number {
NSError* err;
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:#{#"key":number}
options:0
error:&err];
NSString* jsonString = [[NSString alloc]
initWithData:jsonData
encoding:NSUTF8StringEncoding];
return [jsonString containsString:#"true"]
|| [jsonString containsString:#"false"];
}
Swift Version
NSNumber is a class-cluster so each underlying type can be figured from the instance. This code avoids hard-coding the different NSNumber types by creating an instance of the expected type, and then comparing it against the unknown type.
extension NSNumber {
var isBool: Bool {
return type(of: self) == type(of: NSNumber(booleanLiteral: true))
}
}
check object is of NSNumber type :
if([obj isKindOfClass:NSClassFromString(#"__NSCFNumber")])
{
//NSNumber
}

Objective-C Condtions operators weirdness

Okay here's the damned thing:
- (void)setMinimumNumberOfSides:(NSNumber *)newMinimumNumberOfSides {
if (newMinimumNumberOfSides != minimumNumberOfSides) {
NSNumber *minimum = [[NSNumber alloc] initWithInt:(int)2];
if (newMinimumNumberOfSides > minimum) {
[newMinimumNumberOfSides retain];
[minimumNumberOfSides release];
minimumNumberOfSides = newMinimumNumberOfSides;
} else {
NSLog(#"setMinimumNumberOfSides: Invalid number of sides: %# is smaller than the minimum of %# allowed.",
newMinimumNumberOfSides, minimum);
}
[minimum release];
[newMinimumNumberOfSides release];
}
}
There's something weird going on in there! The problem is my if (newMinimumNumberOfSides > minimum) {} condition. Even if newMinimumNumberOfSides is greated than minimum it goes into the else statement.
I did:
NSNumber *minimum = [[NSNumber alloc] initWithInt:(int)6];
[polygon setMinimumNumberOfSides:minimum];
which is way greater than 2. And I receive my error message...
I tried to NSLog those two, and it gives me the right numbers... So what's going on with this?
Thanks a lot!
I think you need to change your if statement to:
if ([newMinimumNumberOfSides intValue] > [minimum intValue])
NSNumber is an object, so you have to get its integer value before you can start using it in comparisons.
You need to use the following method to compare NSNumber objects:
- (NSComparisonResult)compare:(NSNumber *)aNumber
but better yet just convert them to c ints: [myNSNumberValue inValue]