- (void)testStringExample {
// Given
NSString *testString = #"Test";
id mock = OCMClassMock([NSString class]);
OCMStub([mock stringWithContentsOfFile:OCMOCK_ANY
encoding:NSUTF8StringEncoding
error:nil]).andReturn(testString);
// When
NSString *result = [NSString stringWithContentsOfFile:#"SomeFilePath"
encoding:NSUTF8StringEncoding
error:nil];
// Then
XCTAssertEqualObjects(result, testString);
}
As you can see I've been trying to stub an NSString class method using OCMock but don't seem to be having any luck. The test fails indicating that "(null)" is not equal to "Test" but I'm not sure why the mocked method isn't being called correctly. I'm using OCMock 3.1.2 on iOS. Any help greatly appreciated.
Having done a bit more reading, I suspect I've got a similar issue to that shown in the following post:
How to mock class method (+)?
I've subsequently adopted the approach suggested by Christopher Pickslay in the post above and it works a treat.
Related
I am trying to use OCMock library. I am trying to create mock of class object, but it is failing to verify the method. I am unable to understand why the tests are failing.
#interface MyClass:NSObject
+(void) someMethod;
#end
#implementation MyClass
+(void) someMethod
{
NSError* error = nil;
if (![Utility isValidPropWithError:&error])
{
[Logger log:LoggerLevelWarning message:[error localizedDescription] className:className];
}
}
#end
Test :
-(void)testIfLoggerIsset{
id partialMockLogger = OCMClassMock([Logger class]);
id partialMockUtility = OCMClassMock([Utility class]);
id partialMockClass = OCMClassMock([MyClass class]);
NSError *error = nil;
OCMExpect([partialMockUtility isValidPropWithError:&error]);
[MyClass someMethod];
//This works fine.
OCMVerifyAll(partialMockClass);
NSString *className = #"classname";
//This is failing...
OCMVerify([partialMockUtility isValidPropWithError:&error]);
OCMVerifyAll(partialMockUtility);
//This is failing...
OCMVerify([partialMockLogger log:LoggerLevelWarning message:[error localizedDescription] className:className]);
[partialMockUtility stopMocking];
[partialMockLogger stopMocking];
}
In the above code, although [Utility isValidPropWithError:&error]; is called OCMVerify([partialMockUtility isValidPropWithError:&error]);is failing.
Several things here:
First, OCMVerify([partialMockUtility isValidPropWithError:&error] is failing because you are expecting the address of the NSError object you created in the test to be passed to isValidPropWithError:, but in MyClass +someMethod you are creating a different NSError object. The addresses of two different objects will not be the same.
To fix this, change your expectation and verification to:
OCMExpect([partialMockUtility isValidPropWithError:(NSError __autoreleasing**)[OCMArg anyPointer]]);
OCMVerify([partialMockUtility isValidPropWithError:(NSError __autoreleasing**)[OCMArg
and just ignore the actual value of the parameter and expect that it's going to be an NSError pointer (since you're creating it inside of someMethod, there's no way to know what it's going to be before you call the method).
Second, since you are already explicitly verifying +isValidPropWithError, OCMVerifyAll(partialMockUtility) isn't going to verify anything. You should either explicitly verify all of your expectations, or simply use OCMVerifyAll(partialMockUtility) and let it verify all your expectations and don't bother with expecting the specific call. OCMVerifyAll will verify everything you expect on the mock object you give it. This isn't going to cause a test failure - both calls will pass, since you've already verified the call the first time, the call to OCMVerifyAll() isn't going to have anything to verify, so it will pass.
Last, OCMVerify([partialMockLogger log:LoggerLevelWarning message:[error localizedDescription] className:className]); is failing because you didn't set an expectation for it.
I'm writing a unit test to check that the string I pass to the GAITracker class is being returned as the kGAIScreenName property for each screen.
However when I try to pass a sharedInstance to the GAI class to initialize the WNGoogleAnalyticsService instance I am getting the error Thread 1: EXC_BAD_ACCESS (code=1, address=0x20) as if it has not been allocated to the memory. No matter where I try to declare the sharedInstance won't initialize in the test class although it works fine in the AppDelegate.m.
WNGoogleAnlayticsServiceTest.m:
-(void)testIfNoScreenNameExists {
NSString *screenName = #"Screen";
Class builder = [GAIDictionaryBuilder class];
GAI *gai = [GAI sharedInstance];
WNGoogleAnalyticsService *s = [[WNGoogleAnalyticsService alloc] initWithGAInstance:gai
gaKey:#"test"
gaDictionaryBuilderClass:builder
debugging:NO];
id<GAITracker> tracker = [s trackerForScreen:screenName];
XCTAssertEqualObjects([tracker get:kGAIScreenName], screenName);
}
AppDelegate.m:
Add Google Analytics as analytics service
WNGoogleAnalyticsService *googleAnalyticsService = [[WNGoogleAnalyticsService alloc] initWithGAInstance:[GAI sharedInstance]
gaKey:[[NSBundle mainBundle] objectForInfoDictionaryKey:#"WNGoogleKey"]
gaDictionaryBuilderClass:[GAIDictionaryBuilder class]
debugging:analyticsDebugging];
I'm at a loss as to how to even go about fixing this bug so any help would be appreciated.
In line with the comment I left underneath my question, I feel I should formally answer this with my solution.
In the case of this unit test, I was trying to re-use a singleton by reusing the [GAI SharedInstance] method in both my WNGoogleAnlayticsServiceTest and in my AppDelegate.m file which, by definition, it cannot do.
So if you want to test the Google Analytics methods, you must use a tool like OCMock to do so as you can't initialise the sharedInstance twice.
I'm trying to test the URL/path against a request is (or would be) made from a REST-client class using OCMock. The client uses RestKit (which uses AFNetworking) for the communication.
Therefore my plan was to:
Create a block which checks if a AFHTTPRequestOperation URL is the desired one.
Create a partial mock of the of AFHTTPClient.
Mock (stub) the enqueueHTTPRequestOperation: method with the block (of 1.).
Create a mock of RKObjectManager and set its HTTPClient property to the
AFHTTPClient partial-mock (of 2.).
Init an instance of my client-class with the mock-manager (from 4.).
Invoke the method of this instance of which the URL is to be checked.
Verify that enqueueHTTPRequestOperation: was invoked.
I'm not sure if I'm getting OCMock wrong because I couldn't find examples on mocking methods that take one or more arguments like I need to. ...never the less, this is how I tried to achieve the goal:
void (^checkBlock)(NSInvocation *) = ^(NSInvocation *invocation) {
AFHTTPRequestOperation *requestOperation = nil;
[invocation getArgument:&requestOperation atIndex:0];
XCTAssert(requestOperation != nil);
NSString *path = requestOperation.request.URL.path;
XCTAssert([path containsString:#"/rest/user/authenticate"]);
};
AFHTTPClient *httpClientMock = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:FAServerUrl]];
OCMPartialMock(httpClientMock);
[OCMStub([httpClientMock enqueueHTTPRequestOperation:[OCMArg isNotNil]]) andDo:checkBlock];
RKObjectManager *objectManager = OCMClassMock([RKObjectManager class]);
[OCMStub([objectManager HTTPClient]) andReturn:httpClientMock];
FAUserClient *userClient = [[FAUserClient alloc] initWithUser:nil objectManager:objectManager];
[userClient getAccessTokenForUsername:#"testuser"
password:#"pass123"
success:^(NSString *token, NSArray *roles) {
}
failure:^(NSInteger errorCode, NSError *error) {
}];
OCMVerify([httpClientMock enqueueHTTPRequestOperation:OCMOCK_ANY]);
But on [OCMStub([httpClientMock enqueueHTTPRequestOperation:[OCMArg isNotNil]]) andDo:checkBlock]; I get an EXC_BAD_ACCESS (code=1).
Apparently creating the mock-stub (with OCMStub) invokes the to be stubbed method, with the given [OCMArg isNotNil]. I thought A: the parameter just has a declarative meaning and B: this creates a stub and does not invoke the method right away.
Any help or suggestions leading into the "right" direction would be greatly appreciated.
EDIT:
As well tried:
OCMStub([httpClientMock enqueueHTTPRequestOperation:[OCMArg checkWithBlock:^BOOL(id obj) {
AFHTTPRequestOperation *request = (AFHTTPRequestOperation *)obj;
NSString *path = request.request.URL.path;
XCTAssert([path containsString:#"/rest/user/authenticate"]);
return YES;
}]]);
...with the same "result".
Best,
gabriel
Edit
Looked more closely. You are calling OCMPartialMock(httpClientMock). This does not convert the object you call it on, it returns a partial mock. Capture the result in a variable.
AFHTTPClient *httpClientMock = OCPartialMock([[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:FAServerUrl]]);
You should still make the change noted below in your andDo: block. You can also use the "modern" syntax for this:
OCMStub([myObject myMethod]).andDo(myBlock);
Original
I think the issue might be the code in the andDo: block.
[invocation getArgument:&requestOperation atIndex:0];
For all Objective-C methods, NSInvocation has two default arguments, self and _cmd at indexes 0 and 1. Try getting the argument at index 2.
You might also consider including NSInvocation+OCMAdditions which gives you getArgumentAtIndexAsObject . Another alternative is using [OCMArg checkWithBlock:] in which the arg is handed to your evaluation block directly.
I have a NSString that should be constant in my class. I used the following code to accomplish this:
#interface DefinitionViewController ()
#end
static NSString *preamble;
#implementation DefinitionViewController {
}
+(void)initialize {
if (self == [DefinitionViewController class]) {
NSString *path = [[NSBundle mainBundle] pathForResource:#"preamble" ofType:#"html"];
preamble = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding
error:nil];
}
}
It seems to work fine. I worry about using a file read inside an initialize. Is there a more appropriate means to accomplish the same goal (shared static string)? I could bury this inside my code, but it was much easier to maintain the somewhat large string as an external file.
Thanks for any advice.
"I worry about using a file read inside an initialize".
Don't (worry). The fact that it is, for example, a class method is utterly irrelevant. It is code. It runs and does its job. It is sound code, it runs coherently at a coherent time, and your app bundle is a real thing that really contains the resource. There's no problem here.
If you want to postpone creation of the string, and make assurance doubly sure that the string is not initialized twice, you could instead use a singleton pattern so that the string value is not generated until the first time it is explicitly requested:
+ (NSString*) preamble {
static NSString* preamble = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *path = [[NSBundle mainBundle] pathForResource:#"preamble" ofType:#"html"];
preamble = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
});
return preamble;
}
But there is no special need for this. (EDIT: But see, to the contrary, the comment below of #bbum, who Really Knows Stuff.)
I'm working through Cocoa smoothly, but this problem seems so basic it cancels out all the cool stuff I learned. :/
I have a generated file path, and it needs to be in NSURL format. From research, this is the code I wrote:
NSLog(#"Old path = %#", pathToFile);
NSURL *xmlURL = [[[NSURL alloc] init] fileURLWithPath:pathToFile];
NSLog(#"New path = %#", [xmlURL absoluteString]);
And the output:
2010-01-27 15:39:22.105 MusicLibraryStats[28574:a0f] Old path = file://localhost/Users/[username]/Music/iTunes/iTunes%20Music%20Library.xml
2010-01-27 15:39:22.105 MusicLibraryStats[28574:a0f] New path = (null)
First off, the alloc-init shouldn't even be necessary; other people seem to get away with it. In this case, if I don't alloc-init, I get an 'unrecognized selector' error on that line. Of course, now I'm just getting plain old (null).
Where did I goof?
Thanks!
The [[NSURL alloc] init] is not just unnecessary, it's invalid. fileURLWithPath: is a class method, which means you can only call it on the class object (that is, NSURL itself). It does not produce a compile error because -(NSURL *)init returns an object of type id, and does not result in a runtime error because -(NSURL *)init actually returns nil, and messages sent to nil will just cascade another nil as their return value.
This code should work:
NSString* pathToFile = #"/this/is/a/path";
NSURL* url = [NSURL fileURLWithPath:pathToFile];
I found your problem.
-[NSOpenPanel URLs] returns an array of NSURL objects, which you treat as NSString objects. That's not right. You should use the following:
NSURL* url = [[oPanel URLs] objectAtIndex:0];
The debugger could've show you that if you looked at the pathToFile variable. Make sure to check it next time. :) Hovering a variable with your mouse should get you its type.
However, remember that there are situations where you will legitimately encounter another type than the one you expected. For instance, the private NSPathStore2 class is part of the NSString cluster, and you can do everything NSString supports on NSPathStore2 objects. (If this happens and you're not too sure, check the documentation to see if the type you expect is a cluster type. That's how they're called in the documentation.)