Im new to unit testing and OCMock so this might be an obvious answer, just didn't find answer on google.
I am trying to test a model object's method.
the method has the following code:
//takes a filepath and a pk, sets the filepath to the
BoxAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
NSNumber *ifExistIndexInJson = [BoxJsonDataHelper pkExistInCurrentJson:[[[self.downloadQueue objectAtIndex:0] objectForKey:#"pk"] integerValue]];
if (ifExistIndexInJson)
{
[[[self.downloadQueue objectAtIndex:0] objectForKey:#"fields"] setObject:path forKey:#"content"];
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:[[[delegate.currentJsonData objectAtIndex:[ifExistIndexInJson integerValue]] objectForKey:#"fields"] objectForKey:#"content"] error:&error];
[delegate.currentJsonData removeObjectAtIndex:[ifExistIndexInJson integerValue]];
[delegate.currentJsonData addObject:[self.downloadQueue objectAtIndex:0]];
[self.downloadQueue removeObjectAtIndex:0];
if ([self.downloadQueue count] > 0)
{
[BoxServerRequestsObject downloadFileForPK:[[[self.downloadQueue objectAtIndex:0] objectForKey:#"pk"] integerValue]sender:self];
}
else
{
//end the progress or whatever
}
}
else
{
[[[self.downloadQueue objectAtIndex:0] objectForKey:#"fields"] setObject:path forKey:#"content"];
[delegate.currentJsonData addObject:[self.downloadQueue objectAtIndex:0]];
[self.downloadQueue removeObjectAtIndex:0];
if ([self.downloadQueue count] > 0)
{
[BoxServerRequestsObject downloadFileForPK:[[[self.downloadQueue objectAtIndex:0] objectForKey:#"pk"] integerValue]sender:self];
}
else
{
//end the progress or whatever
}
}
I need help with a couple of things:
when I call [BoxJsonDataHelper pkExistInCurrentJson:...]. BoxJsonDataHelper is actually self, only it's a class method not an instance, so I call it by name, How can I fake the results of the return value so theres no dependency?
How to fake a file at a path for the program to remove? than how do I check that it was removed?
how do I mock BoxServerRequestObject to make the method call the mock object instead of the real one? and than how do I check if it has been called(also a class method)
My knowledge in unit testing is limited, and I have just started with OCMock and read some examples so I would appreciate full answers :)
You can mock class methods just like instance methods. They stay mocked until the mock is dealloc'ed.
id boxJsonDataHelperMock = [OCMockObject mockForClass:BoxJsonDataHelper.class];
[[[boxJsonDataHelperMock stub] andReturn:#(1)] pkExistInCurrentJson:OCMOCK_ANY]
Are you just testing whether NSFileManager works at that point? With data objects, I prefer to do the actual writing. Why not just assert that the file doesn't exist after it is removed? If you wanted to mock, you should mock "defaultManager" on NSFileManager and return a mock object that expects removeItemAtPath:error:
Place a mock object in your download queue at index 0.
Related
I am facing some hard time in mocking a block inside one of my methods which i want to test.
Below is more or less how my code looks like
- (void) startFetching:(MyParameter *) parameter
{
self.fetcher = [[MyFetcher alloc] initWithContext:xxxx andObserver:nil];
self.fetcher.parameters = #[parameter];
[self.fetcher startWithCompleteionBlock:^(id<MyOperation> _Nonnull operation) {
if(operation.errors.count > 0) {
[self.delegate failedWithError:operation.errors.firstObject];
} else{
FetcherResponse *response = [MyFetcherResponse cast:operation];
NSArray *array = response.responseArray;
if(array.count == 1) {
[self.delegate completedWithSuccess:array.firstObject];
}
}
}];
}
Now i have a test method like testStartFetching and i want to test this method. i don't understand how i can stub this part [self.fetcher startWithCompleteionBlock:^(id<MyOperation> _Nonnull operation) inside my method so that, in success case it return proper array and in failure case it return errors and if i stub it for with errors then failedWithError:operation is called and completedWithSuccess is called otherwise.
I am using OCMock framework in objective c and i am new to unit testing. Any help will be highly appreciated.
I stub method with completion block which returns operation (with errors). Then I verify that calls delegate's method - failedWithError with right parameter (error).
id<MyOperation> operation = [[MyClassOperaion alloc] init];
NSError *error = [NSError new];
operation.errors = #[error];
OCMStub([self.fetcher startWithCompleteionBlock:([OCMArg checkWithBlock:^BOOL(void(^passedBlock)(id<MyOperation> _Nonnull operation)) {
passedBlock(operation);
return YES;
}])]);
OCMVerify([self.delegate failedWithError:error]);
I am starting in Unit testing with objective-c and I need to know how to test blocks with OCMockito and Xcode 6.
I am testing an Interactor, this interactor should return an array as a block argument and I has to ask the Provider file for the elements.
This is the method I want to test:
- (void)userPoiListsWithSuccessBlock:(MNBSavePoisInteractorSuccess)success {
self.poiListEntityArray = [self.poiListProvider poiListsForUser:self.loggedUser];
self.poiListViewObjectArray = [self viewPoiListObjectListWithPoiLists:self.poiListEntityArray];
success(self.poiListViewObjectArray);
}
First, I setup the elements that I am going to use
self.mockPoiListProvider = mock([PoiListProvider class]);
self.sut = [[MNBSavePoisInteractor alloc] initWithManagedObjectContext:self.coreDataStack.managedObjectContext andPoiListProvider:self.mockPoiListProvider];
- (UserEntity *)loggedUserMock {
UserEntity *mockLoggedUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([UserEntity class]) inManagedObjectContext:self.coreDataStack.managedObjectContext];
mockLoggedUser.userId=#"1";
mockLoggedUser.username=#"user";
mockLoggedUser.loggedUser=#YES;
return mockLoggedUser;
}
- (InMemoryCoreDataStack *)coreDataStack{
if (!_coreDataStack) {
_coreDataStack = [[InMemoryCoreDataStack alloc] init];
}
return _coreDataStack;
}
- (PoiListEntity *)poiListFake {
PoiListEntity *fake = [NSEntityDescription insertNewObjectForEntityForName:#"PoiListEntity" inManagedObjectContext:self.coreDataStack.managedObjectContext];
fake.name = #"Test";
fake.poisCount = #2;
[fake addContributorsObject:[self loggedUserMock]];
return fake;
}
Then, I do the test. I am using Xcode 6 waitForExpectation to manage the asynchronous methods. I think I am doing something wrong.
- (void)waitForExpectation {
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
if (error) {
NSLog(#"Timeout Error: %#", error);
}
}];
}
- (void)testShouldReturnPoiLists {
XCTestExpectation *expectation = [self expectationWithDescription:#"Waiting method ends"];
[given([self.mockPoiListProvider poiListsForUser:[self loggedUserMock]]) willReturn:#[[self poiListFake]]];
[self.sut userPoiListsWithSuccessBlock:^(NSArray *results) {
[expectation fulfill];
XCTAssert(resutls.count == 1, #"Results %zd", resutls.count);
}];
[self waitForExpectation];
}
I understood if I give the object in willReturn in the given method, when I call the sut method that I want to test it should return what I give before. Is that true?
Thank you
I see no asynchronous code. You just want a block that captures the results, so use a __block variable to make the results available outside of the block. Then you can assert whatever you want:
- (void)testShouldReturnPoiLists {
[given([self.mockPoiListProvider poiListsForUser:[self loggedUserMock]]) willReturn:#[[self poiListFake]]];
__block NSArray *capturedResults;
[self.sut userPoiListsWithSuccessBlock:^(NSArray *results) {
capturedResults = results;
}];
assertThat(capturedResults, hasLengthOf(1));
}
The relationship between the length of 1 and the fake is hard to tell. Let's also parameterize the faking code:
- (PoiListEntity *)poiListFakeWithName:(NSString *)name count:(NSNumber *)count {
PoiListEntity *fake = [NSEntityDescription insertNewObjectForEntityForName:#"PoiListEntity" inManagedObjectContext:self.coreDataStack.managedObjectContext];
fake.name = name;
fake.poisCount = count;
[fake addContributorsObject:[self loggedUserMock]];
return fake;
}
With that, we can write more interesting tests.
I do want to add that it's important to "listen to the tests." There's a lot of convoluted set-up to dance around Core Data. That tells me that if you can rewrite things to be independent of Core Data — completely ignorant of it — everything will be much simpler.
I'm completely new to the Reactive Cocoa Framework and i'm just doing some simple tests but i'm encountering a problem that i would like to understand.
Basically i'm just doing an API call to fetch a JSON object from my server, i want to do this with RAC. So my steps are the following:
First i build the RACCommand this way:
RACCommand *getLatestVersionCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [[[API sharedInstance] getLatestAppVersion]
materialize];
}];
Now i create the signal and subscribe to it this way:
RACSignal *versionCodeSignal = [[getLatestVersionCommand.executionSignals flatten] deliverOn:[RACScheduler mainThreadScheduler]];
[[versionCodeSignal
map:^id(NSDictionary *responseObject) {
return responseObject;
}]
subscribeNext:^(NSDictionary *responseObject) {
NSArray *allVersions = [[NSArray alloc] initWithArray:[responseObject objectForKey:KEY_VERSIONS]];
for(NSDictionary *version in allVersions) {
NSString *device = [version objectForKey:KEY_DEVICE];
if([device isEqualToString:KEY_IOS]) {
NSString *latestVersionName = [[version objectForKey:KEY_VERSION] objectForKey:KEY_NAME];
if([APP_VERSION compare:latestVersionName options:NSNumericSearch] == NSOrderedAscending) {
//There is a new version!
NSLog(#"There is a new version!!!");
}
}
}
}];
Finally I execute the command this way
[getLatestVersionCommand execute:self];
The problem i'm facing is that in the subscribeNext block, the object i receive is an RACEvent object and not the dictionary i'm expecting. I know i'm doing something wrong and not understanding the full flow correctly, i tried adding the map function that i thought i didn't need just to test and nothing.
The only way i found is to convert the response in the map block to a RACEvent and return its value but that doesn't seem to me the right way to go.
Any light will be greatly appreciated.
Remove the call to -[RACSignal materialize] from your command's signal block.
I'm (sort of) following the Stanford CS193P ios class and I'm trying to get a document context without having to pass from controller to controller. Prof Haggerty uses this method to get his context, but it doesn't work for me. I'm spelling everything correctly and I can get the context when I pass it, but not when I get it this way.
Am I missing something?? I just want to get the context for the database that I know I've created without having to pass in.s
- (void)useDemoDocument
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Demo Document"];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
[self refresh];
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else {
self.managedObjectContext = document.managedObjectContext;
}
}
I don't think you misspelled anything, but you might have the wrong expectation of what that method is doing. The method is not returning any context. It sets the context as a class property (but only in some cases!).
So after calling this method you should be able to access the self.managedObjectContext property.
However.. The method implementation is not very clean and therefore a bit dangerous. It will set the 'managedObjectContext' property only for successful scenarios. The method does not clear the self.managedObjectContext property in other cases, so in those cases it's unclear what the self.managedObjectContext is pointing to.
Since the method does not provide any success status you can never trust if the managedObjectContext has been set properly.
I had the same problem (also doing Stanford class). I've since found the solution, you can find it here iOS 7 Completion handler never gets called
OpenWithCompletionHandler is asynchronous, so the application might try to use the context even before completion handler block finishes executing. Hence, it seems like the method doesn't work. Using a run loop will solve your problem.
NSString *pictureUrl = [[[oneUserDict objectForKey:#"picture"]objectForKey:#"data"]objectForKey:#"url"];
[[AppEngine sharedEngine]imageAtURL:[NSURL URLWithString:pictureUrl] onCompletion:^(UIImage *fetchedImage, NSURL *url, BOOL isInCache)
{
int index = [usersArray indexOfObject:oneUserDict];
NSString *loadName = [NSString stringWithFormat:#"%d of %d",index,[usersArray count]];
NSLog(#"%i",usersArray.count);
int temp=[usersArray count]-10;
if (index!=temp)
{
[[LoadingIndicator currentIndicator]displayActivity:loadName];
NSLog(#"inside loading indicator");
}
else
{
[[LoadingIndicator currentIndicator]displayCompleted:#"Done"];
NSLog(#"finally done");
}
aPerson.image = UIImagePNGRepresentation(fetchedImage);
[appDelegate.managedObjectContext save:nil];
}];
AppEngine is the subclass of MKNetworkEngine which uses a method called imageAtURL:onCompletion:
what I am currently doing is retrieving all the images from a particular url and and storing them in aPerson.image,basically the above code is in a FOR loop(i.e the for the count of users).
Issues
The above code which is in the completion block never gets executed,i dont know why but i have put a breakpoint inside the block but still the compiler wont run the statements inside the completion block.
Api imageAtURL:onCompletion: is deprecated. Use imageAtURL:completionHandler:errorHandler: instead. Also MKNetworkKit provides for UIImageView+MKNetworkKitAdditions category which provides simple API for image download like setImageFromURL: placeHolderImage:
Cheers!
Amar.