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];
};
Related
+(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).
I'm parsing some json to return a basic string token or error message.
- (void)callBackWithVerifyHttpResponse:(NSData *)response
{
SomeResult *result = [self.parser parseVerifyHttpResponseAndReturnResult:response];
if (result.token) { [self.delegate callBackWithToken:result.token]; }
if (result.error) { [self.delegate callBackWithError:result.error]; }
}
The tests that prove this
- (void)testVerifyCallbackInvokesErrorCallbackOnDelegateWhenParserReturnsError
{
SomeResult *result = [[SomeResult alloc] init];
result.error = #"fail";
[[self.delegate expect] callBackWithError:#"fail"];
[[self.delegate reject] callBackWithToken:OCMArg.any];
[[[self.parser stub] andReturn:result] parseVerifyHttpResponseAndReturnResult:nil];
[self.sut callBackWithVerifyHttpResponse:nil];
[self.delegate verify];
}
- (void)testVerifyCallbackInvokesTokenCallbackOnDelegateWhenParserReturnsToken
{
SomeResult *result = [[SomeResult alloc] init];
result.token = #"token";
[[self.delegate expect] callBackWithToken:#"token"];
[[self.delegate reject] callBackWithError:OCMArg.any];
[[[self.parser stub] andReturn:result] parseVerifyHttpResponseAndReturnResult:nil];
[self.sut callBackWithVerifyHttpResponse:nil];
[self.delegate verify];
}
All was well until I wired this up to an actual endpoint -only to find that unless I modified the callback like the below -it was calling both callbacks (not what I was hoping for)
- (void)callBackWithVerifyHttpResponse:(NSData *)response
{
SomeResult *result = [self.parser parseVerifyHttpResponseAndReturnResult:response];
if (result.token != [NSNull null]) { [self.delegate callBackWithToken:result.token]; }
if (result.error != [NSNull null]) { [self.delegate callBackWithError:result.error]; }
}
So 2 part question
why can't I write a test to prove this? Any time I set the error or token to NULL or NSNull it works fine (yet this code was required for production to work)
why would the production code only fail the conditional if I put the != [NSNull null] (yet I can't seem to get anything but <null> when I NSLog the values while I run it in the simulator?
Keep in mind the token/error properties look like this on the SomeResult object
#interface SomeResult : NSObject
#property (strong, nonatomic) NSString *token;
#property (strong, nonatomic) NSString *error;
#end
Your original code and tests are expecting either token or error to be nil, not [NSNull null]. Presumably when you run it against a production endpoint, your parser is setting the value to [NSNull null].
These tests should pass with your modified code:
- (void)testVerifyCallbackInvokesErrorCallbackOnDelegateWhenParserReturnsError
{
SomeResult *result = [[SomeResult alloc] init];
result.error = #"fail";
result.token = [NSNull null];
[[self.delegate expect] callBackWithError:#"fail"];
[[self.delegate reject] callBackWithToken:OCMArg.any];
[[[self.parser stub] andReturn:result] parseVerifyHttpResponseAndReturnResult:nil];
[self.sut callBackWithVerifyHttpResponse:nil];
[self.delegate verify];
}
- (void)testVerifyCallbackInvokesTokenCallbackOnDelegateWhenParserReturnsToken
{
SomeResult *result = [[SomeResult alloc] init];
result.token = #"token";
result.error = [NSNull null];
[[self.delegate expect] callBackWithToken:#"token"];
[[self.delegate reject] callBackWithError:OCMArg.any];
[[[self.parser stub] andReturn:result] parseVerifyHttpResponseAndReturnResult:nil];
[self.sut callBackWithVerifyHttpResponse:nil];
[self.delegate verify];
}
When you're dealing with a web service and/or a third party parser that could change its behavior subtly, it's good to code defensively. You could handle both nil and NSNull:
- (void)callBackWithVerifyHttpResponse:(NSData *)response
{
SomeResult *result = [self.parser parseVerifyHttpResponseAndReturnResult:response];
if (result.token && result.token != [NSNull null]) { [self.delegate callBackWithToken:result.token]; }
if (result.error && result.error != [NSNull null]) { [self.delegate callBackWithError:result.error]; }
}
You may also want to make this an if/else--either test for error first, and never set the token if there's an error, or ignore the error if you get a token. If you don't want to ever call both callbacks, then make it impossible in your code paths.
I am trying to set up my project TDD, so I've got the following unit test:
- (void)testOnDoesUsernameExistsShouldReturnFalseWhenInvalidJSONResponseFromService {
id mock = [OCMockObject partialMockForObject:(NSObject *) (id <ServiceHelperProtocol>) self.serviceHelper];
[[[mock stub] andReturn:#"invalid-json-response"] get:[OCMArg any]];
assertThat([self.signUpService doesUsernameExist:#"testusername"], true);
}
This test runs against this method:
- (BOOL)doesUsernameExist:(NSString *)userName {
NSString *url = [[NSString alloc] initWithFormat:#"%#/%#/api/signup.username_exists/?username=%#", kAPPLICATION_HOST, kAPPLICATION_NAME, userName];
NSString *responseString = [self.serviceHelper get:url];
if (responseString != nil && ![responseString isEqualToString:#""]) {
#try {
NSDictionary *dictionary = [responseString JSONValue]; // auto-released
NSString *usernameExists = [dictionary objectForKey:#"username_exists"];
return usernameExists != nil && ![usernameExists isEqualToString:#"null"];
}
#catch (NSException *e) {
NSLog(#"Unable to parse JSON: %#", e.description);
}
}
[responseString release];
[url release];
return false;
}
When I run this test, I get the following warning on line 94 (the assertThat line):
passing argument 2 of 'HC_assertThatWithLocation' makes pointer from integer without a cast
When I substitute the macro I get this line of code:
HC_assertThatWithLocation(self, [self.signUpService doesUsernameExist:#"testusername"], HC_is(0), "_file_name_", 0);
For some reason my test fails, but I can't figure out what is wrong. Is the test case wrong, is the implemented method wrong or does it has something to do with the OCMock framework in conjunction with the OCHamcrest matchers ?
I'm using Xcode 4.2 with both OCMock 1.66 and OCHamcrest 1.7.
Any thoughts ?
Whenever I build and run I get no errors or warnings. Then I tried setting NSZombieEnabled. I get the following crash:
2011-12-06 16:08:46.869 APITextProject[4194:207] * -[APITextProjectViewController dealloc]: message sent to deallocated instance 0x5a31480
Here is my code:
#pragma ASI Delegate methods
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSLog(#"Request finished successfully");
NSLog(#"%#",[request responseString]);
NSDictionary *responseDictionary = [[request responseString]JSONValue];
NSDictionary *arrayElement = [responseDictionary objectForKey:#"user"];
NSString *ID = [arrayElement valueForKeyPath:#"id"];
NSLog(#"id: %d",ID);
NSString *usr = [arrayElement valueForKeyPath:#"usr"];
NSLog(#"usr: %#",usr);
NSString *gd = [arrayElement valueForKeyPath:#"gd"];
NSLog(#"gd: %#",gd);
NSString *age = [arrayElement valueForKeyPath:#"ag"];
NSLog(#"ag: %#",age);
NSString *st = [arrayElement valueForKeyPath:#"st"];
NSLog(#"st: %#",st);
NSString *lf = [arrayElement valueForKeyPath:#"lf"];
NSLog(#"lf: %#",lf);
NSString *da = [arrayElement valueForKeyPath:#"da"];
NSLog(#"da: %d",da);
for(NSString *value in [arrayElement allValues]){
NSLog(#"Found Value %#",value);
label.text = value;
[super release];
}
}
Please delete this line.
[super release];
It should NEVER make sense to call release on super.
I have a feeling your problem lies in the line [super release] inside the for loop.
Why are you calling [super release] in your request delegate method? Why would you ever call super release?
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.