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.
Related
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.
Update
After posting this as an issue to the AFNetworking repo, turns out this is in fact a usage issue on my part. Per the response to my issue:
NSURLSession retains its delegate (i.e. AFURLSessionManager). Call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate.
So, long story short: If you are using AHTTPSessionManager in the manner described below, make sure to call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate
Original Question
I have a subclassed AFHTTPSessionManager called GTAPIClient that I am using to connect to my REST API. I realize the docs state to use as a singleton but there are a few cases where I need to gin up a new instance. However, it seems that whenever I do so, the object is never deallocated. Currently, GTAPIClient literally does nothing except NSLog itself when deallocated.
Here's some sample code that demonstrates the behavior
GTAPIClient.m
#implementation GTAPIClient
- (void)dealloc
{
NSLog(#"Dealloc: %#", self);
}
#end
GTViewController.m
#import "GTBaseEntityViewController.h"
//Models
#import "GTBaseEntity.h"
//Clients
#import "GTAPIClient.h"
#interface GTBaseEntityViewController ()
#property (nonatomic, weak) GTAPIClient *client;
#property (nonatomic, weak) GTBaseEntity *weakEntity;
#end
#implementation GTBaseEntityViewController
- (IBAction)makeClient:(id)sender {
self.client = [[GTAPIClient alloc] init];
NSLog(#"I just made an API client %#", self.client);
//Another random object assigned to a similar property, just to see what happens.
self.weakEntity = [[GTBaseEntity alloc] init];
NSLog(#"I just made a random object %#", self.weakEntity);
}
- (IBAction)checkClient:(id)sender {
NSLog(#"Client: %#", self.client);
NSLog(#"Entity: %#", self.weakEntity);
}
#end
NSLog output
Fire makeClient:
//It seems to me that both NSLog's should return (null) as they are assigning to a weak property
2014-06-22 16:41:39.143 I just made an API client <GTAPIClient: 0x10b913680, baseURL: (null), session: <__NSCFURLSession: 0x10b915010>, operationQueue: <NSOperationQueue: 0x10b9148a0>{name = 'NSOperationQueue 0x10b9148a0'}>
2014-06-22 16:41:39.144 I just made a random object (null)
Fire checkClient
//Again, both NSLog's should return null for the two objects. However...client is still around. Also, it's overridden dealloc method never fired.
2014-06-22 16:44:43.722 Client: <GTAPIClient: 0x10b913680, baseURL: (null), session: <__NSCFURLSession: 0x10b915010>, operationQueue: <NSOperationQueue: 0x10b9148a0>{name = 'NSOperationQueue 0x10b9148a0'}>
2014-06-22 16:44:43.723 Entity: (null)
For reference, I am using v2.3.1 of AFNetworking. Compiler is warning me that assigning retained object to weak property will release after assignment - which is correct, and functions as expects with my random object. There is nothing else going on in the app. No other view controllers, no other methods on the GTAPIClient, all singleton functionality is removed. Any thoughts on what I am doing wrong here?
Posting the response from Mattt Thompson here to assist future readers:
NSURLSession retains its delegate (i.e. AFURLSessionManager). Call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate.
If, like many apps, your app uses a singleton Session Manager and one URL Session for your entire app, then you don't need to worry about this.
Replicating your scenario and running it through Instruments shows that AFURLSessionManagers are retained by the NSURLSessions they create, as AFURLSessionManager acts as the delegate for every NSURLSession created. This creates a retain cycle and thus the AFHTTPSessionManager cannot be released. Whether this is a bug in either library or not a bug at all, I'm not sure. You may want to report it on the AFNetworking GitHub page (https://github.com/AFNetworking/AFNetworking).
__weak typeof(manager) weak_manager = manager;
[manager requestWithMethod:method URLString: uri parameters:param
success:^(NSURLSessionDataTask *task, id responseObject) {
if (completion) {
completion(YES, responseObject, task.response);
}
[weak_manager invalidateSessionCancelingTasks:YES];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completion) {
completion(NO, error, task.response);
}
[weak_manager invalidateSessionCancelingTasks:YES];
}];
I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.
The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.
Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:
Interface:
#interface FileManager : NSObject <NSFilePresenter>
#property (unsafe_unretained) IBOutlet NSTextView *textView;
- (void) saveFile;
- (void) reloadFile;
#end
Implementation:
#implementation FileManager
{
NSOperationQueue* queue;
NSURL* fileURL;
}
- (id) init {
self = [super init];
if (self) {
self->queue = [NSOperationQueue new];
self->fileURL = [NSURL URLWithString:#"/Users/Jonathan/file.txt"];
[NSFileCoordinator addFilePresenter:self];
}
return self;
}
- (NSURL*) presentedItemURL {
NSLog(#"presentedItemURL");
return self->fileURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
NSLog(#"presentedItemOperationQueue");
return self->queue;
}
- (void) saveFile {
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
[coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
NSString* content = [self.textView string];
[content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}];
}
- (void) reloadFile {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
__block NSData* content;
[coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
if ([fileManager fileExistsAtPath:[url path]]) {
content = [fileManager contentsAtPath:[url path]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
});
}
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
#end
Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).
The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.
Can anyone tell me what I'm doing wrong here?
I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.
I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.
(I realize that this is an old question, but... :) )
First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).
Secondly, you wrote:
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.
- (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
{
// implement your save routine here, but only if you need to!
if ( dataHasChanged ) [self save]; // <-- meta code
//
NSError * err = nil; // <-- = no error, in this simple implementation
completionHandler(err); // <-- essential!
}
I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)
H guys,
I have been trying for ages to find some good examples on how to use Kiwi testing to test delegate methods, asynchronously.
I have a manager class that defines the protocols for testing, with a pass and fail method returned in the delegate. Can anyone provide sample code on how to do this? Can I make the test class itself implement the to call the methods on the manager?
Thanks guys
You can do like in example
SPEC_BEGIN(IFStackOverflowRequestSpec)
describe(#"IFStackOverflowRequestSpec", ^
{
context(#"question request", ^
{
__block IFViewController *controller = nil;
beforeEach(^
{
controller = [IFViewController mock];
});
it(#"should conform StackOverflowRequestDelegate protocol", ^
{
[[controller should] conformToProtocol:#protocol(StackOverflowRequestDelegate)];
});
it(#"should recieve receivedJSON", ^
{
NSString *questionsUrlString = #"http://api.stackoverflow.com/1.1/search?tagged=iphone&pagesize=20";
IFStackOverflowRequest *request = [[IFStackOverflowRequest alloc] initWithDelegate:controller urlString:questionsUrlString];
[[request fetchQestions] start];
[[[controller shouldEventuallyBeforeTimingOutAfter(3)] receive] receivedJSON:any()];
});
it(#"should recieve fetchFailedWithError", ^
{
NSString *fakeUrl = #"asda";
IFStackOverflowRequest *request = [[IFStackOverflowRequest alloc] initWithDelegate:controller urlString:fakeUrl];
[[request fetchQestions] start];
[[[controller shouldEventuallyBeforeTimingOutAfter(1)] receive] fetchFailedWithError:any()];
});
});
});
Full example can be founded on this link.
You can do what I think you're trying to achieve by creating a mock object that stands in for the delegate, and then checking that the mock object receives the delegate callbacks that you expect. So the process would look like:
create a mock object that conforms to the delegate protocol:
id delegateMock = [KWMock mockForProtocol:#protocol(YourProtocol)];
set the mock as the delegate of your manager class:
[manager setDelegate:delegateMock];
create an object containing the data that will be returned by your manager class:
NSString *response = #"foo";
set the assertion that the mock should eventually be called with the method and response object (in this case, I'm expecting to receive managerRepliedWithResponse and foo)
[[[delegateMock shouldEventually] receive] managerRepliedWithResponse:response];
call the method under test:
[manager performMyMethod];
The key is setting the expectation before you call the method, and using shouldEventually which delays the assertion being checked and gives the manager object time to perform the method.
There's a range of expectations you can also use that are listed on the Kiwi wiki - https://github.com/allending/Kiwi/wiki/Expectations
I've written the process up in more detail in a post on my site, albeit that it's more specifically geared-up to the situation I was dealing with.
Using iOS 5's new TWRequest API, I've ran into a brick wall related with block usage.
What I need to do is upon receiving a successful response to a first request, immediately fire another one. On the completion block of the second request, I then notify success or failure of the multi-step operation.
Here's roughly what I'm doing:
- (void)doRequests
{
TWRequest* firstRequest = [self createFirstRequest];
[firstRequest performRequestWithHandler:^(NSData* responseData,
NSHTTPURLResponse* response,
NSError* error) {
// Error handling hidden for the sake of brevity...
TWRequest* secondRequest = [self createSecondRequest];
[secondRequest performRequestWithHandler:^(NSData* a,
NSHTTPURLResponse* b,
NSError* c) {
// Notify of success or failure - never reaches this far
}];
}];
}
I am not retaining either of the requests or keeping a reference to them anywhere; it's just fire-and-forget.
However, when I run the app, it crashes with EXC_BAD_ACCESS on:
[secondRequest performRequestWithHandler:...];
It executes the first request just fine, but when I try to launch a second one with a handler, it crashes. What's wrong with that code?
The methods to create the requests are as simple as:
- (TWRequest*)createFirstRequest
{
NSString* target = #"https://api.twitter.com/1/statuses/home_timeline.json";
NSURL* url = [NSURL URLWithString:target];
TWRequest* request = [[TWRequest alloc]
initWithURL:url parameters:params
requestMethod:TWRequestMethodGET];
// _twitterAccount is the backing ivar for property 'twitterAccount',
// a strong & nonatomic property of type ACAccount*
request.account = _twitterAccount;
return request;
}
Make sure you're keeping a reference/retaining the ACAccountStore that owns the ACAccount you are using to sign the TWRequests.
If you don't, the ACAccount will become invalid and then you'll get EXC_BAD_ACCESS when trying to fire a TWRequest signed with it.
I'm not familiar with TW*, so consider this a wild guess ... try sending a heap-allocated block:
[firstRequest performRequestWithHandler:[^ (NSData *responseData, ...) {
...
} copy]];
To clarify, I think the block you're sending is heap-allocated, so while TW* might be retaining it, it won't make any difference if it has already gone out of scope.