// // RKPaginatorTest.m // RestKit // // Created by Blake Watters on 12/29/11. // Copyright (c) 2009-2012 RestKit. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #import "RKTestEnvironment.h" #import "RKPaginator.h" #import "RKObjectMapperTestModel.h" #import "RKURLEncodedSerialization.h" @interface RKPaginator (Testability) - (void)waitUntilFinished; @end @interface RKPaginatorTest : RKTestCase @property (nonatomic, readonly) NSURL *paginationURL; @property (nonatomic, readonly) RKObjectMapping *paginationMapping; @property (nonatomic, readonly) RKResponseDescriptor *responseDescriptor; @end @implementation RKPaginatorTest static NSString * const RKPaginatorTestResourcePathPattern = @"/paginate?per_page=:perPage&page=:currentPage"; static NSString * const RKPaginatorTestResourcePathPatternWithOffset = @"/paginate?limit=:perPage&offset=:offset"; - (void)setUp { [RKTestFactory setUp]; } - (void)tearDown { [RKTestFactory tearDown]; } - (RKResponseDescriptor *)responseDescriptor { RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKObjectMapperTestModel class]]; [mapping addAttributeMappingsFromArray:@[@"name", @"age"]]; return [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:nil keyPath:@"entries" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]; } - (RKObjectMapping *)paginationMapping { RKObjectMapping *paginationMapping = [RKObjectMapping mappingForClass:[RKPaginator class]]; [paginationMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"current_page" toKeyPath:@"currentPage"]]; [paginationMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"per_page" toKeyPath:@"perPage"]]; [paginationMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"total_entries" toKeyPath:@"objectCount"]]; [paginationMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"offset" toKeyPath:@"offset"]]; return paginationMapping; } - (NSURL *)paginationURL { return [NSURL URLWithString:RKPaginatorTestResourcePathPattern relativeToURL:[RKTestFactory baseURL]]; } - (NSURL *)paginationOffsetURL { return [NSURL URLWithString:RKPaginatorTestResourcePathPatternWithOffset relativeToURL:[RKTestFactory baseURL]]; } #pragma mark - Test Cases - (void)testInitCopiesPatternURL { NSURL *patternURL = [NSURL URLWithString:@"http://restkit.org"]; NSURLRequest *request = [NSURLRequest requestWithURL:patternURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; expect([paginator.patternURL absoluteString]).to.equal(@"http://restkit.org"); } - (void)testInitDoesNotHavePageCount { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; expect([paginator hasPageCount]).to.equal(NO); } - (void)testInitDoesNotHaveObjectCount { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; expect([paginator hasObjectCount]).to.equal(NO); } - (void)testThatInitWithInvalidPaginationMappingRaisesError { RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; NSException *exception = nil; @try { RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:mapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; } @catch (NSException *e) { exception = e; } expect(exception).notTo.beNil(); expect([exception reason]).to.equal(@"The paginationMapping must have a target object class of `RKPaginator`"); } - (void)testThatInitWithNilResponseDescriptorsRaisesError { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; NSException *exception = nil; @try { RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:nil]; [paginator loadPage:1]; } @catch (NSException *e) { exception = e; } expect(exception).notTo.beNil(); expect([exception reason]).to.equal(@"Invalid parameter not satisfying: responseDescriptors"); } - (void)testThatResourcePathPatternEvaluatesAgainstPaginator { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; NSUInteger currentPage = 1; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; expect([paginator.URL relativeString]).to.equal(@"/paginate?per_page=25&page=1"); } - (void)testThatURLReturnedReflectsStateOfPaginator { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; NSUInteger currentPage = 1; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; expect([[mockPaginator URL] query]).to.equal(@"per_page=25&page=1"); } - (void)testThatURLReturnedReflectsStateOfPaginatorWithOffset { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationOffsetURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; NSUInteger currentPage = 1; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; expect([[mockPaginator URL] query]).to.equal(@"limit=25&offset=0"); } - (void)testLoadingAPageOfObjects { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect(paginator.isLoaded).to.equal(YES); } - (void)testLoadingAPageOfObjectsWithDocumentedPaginationMapping { RKObjectMapping *paginationMapping = [RKObjectMapping mappingForClass:[RKPaginator class]]; [paginationMapping addAttributeMappingsFromDictionary:@{ @"per_page": @"perPage", @"total_pages": @"pageCount", @"total_objects": @"objectCount", }]; NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect(paginator.isLoaded).to.equal(YES); } - (void)testLoadingPageOfObjectMapsPerPage { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect(paginator.perPage).to.equal(3); } - (void)testLoadingPageOfObjectMapsTotalEntries { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect(paginator.objectCount).to.equal(6); } - (void)testLoadingPageOfObjectMapsCurrentPage { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect(paginator.currentPage).to.equal(1); } - (void)testLoadingPageOfObjectMapsEntriesToObjects { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect([paginator.mappingResult array]).to.haveCountOf(3); NSArray *expectedNames = @[ @"Blake", @"Sarah", @"Colin" ]; expect([[paginator.mappingResult array] valueForKey:@"name"]).to.equal(expectedNames); } - (void)testLoadingPageOfObjectHasPageCount { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect([paginator hasPageCount]).to.beTruthy(); } - (void)testLoadingPageOfObjectHasObjectCount { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect([paginator hasObjectCount]).to.beTruthy(); } - (void)testInvocationOfCompletionBlockWithSuccess { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; __block NSArray *blockObjects = nil; [paginator setCompletionBlockWithSuccess:^(RKPaginator *paginator, NSArray *objects, NSUInteger page) { blockObjects = objects; } failure:nil]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect(blockObjects).willNot.beNil(); } - (void)testOnDidFailWithErrorBlockIsInvokedOnError { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; __block NSError *expectedError = nil; [paginator setCompletionBlockWithSuccess:nil failure:^(RKPaginator *paginator, NSError *error) { expectedError = error; }]; [paginator loadPage:999]; [paginator waitUntilFinished]; expect(expectedError).willNot.beNil(); } - (void)testInvocationOfCompletionBlockWithoutWaiting { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; __block NSArray *blockObjects = nil; [paginator setCompletionBlockWithSuccess:^(RKPaginator *paginator, NSArray *objects, NSUInteger page) { blockObjects = objects; } failure:nil]; [paginator loadPage:1]; expect(blockObjects).willNot.beNil(); } - (void)testLoadingNextPageOfObjects { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect(paginator.currentPage).to.equal(1); [paginator loadNextPage]; [paginator waitUntilFinished]; expect(paginator.currentPage).to.equal(2); NSArray *names = @[ @"Asia", @"Roy", @"Lola" ]; expect([[paginator.mappingResult array] valueForKey:@"name"]).to.equal(names); } - (void)testLoadingPreviousPageOfObjects { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:2]; [paginator waitUntilFinished]; expect(paginator.currentPage).to.equal(2); [paginator loadPreviousPage]; [paginator waitUntilFinished]; expect(paginator.currentPage).to.equal(1); NSArray *names = @[ @"Blake", @"Sarah", @"Colin" ]; expect([[paginator.mappingResult array] valueForKey:@"name"]).to.equal(names); } - (void)testFailureWhenLoadingAPageOfObjects { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:3]; [paginator waitUntilFinished]; expect(paginator.error).notTo.beNil(); } - (void)testKnowledgeOfHasANextPage { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; BOOL isLoaded = YES; NSUInteger perPage = 5; NSUInteger pageCount = 3; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(isLoaded)] isLoaded]; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(perPage)] perPage]; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(pageCount)] pageCount]; NSUInteger currentPage = 1; [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; assertThatBool([mockPaginator hasNextPage], is(equalToBool(YES))); currentPage = 2; [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; assertThatBool([mockPaginator hasNextPage], is(equalToBool(YES))); currentPage = 3; [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; assertThatBool([mockPaginator hasNextPage], is(equalToBool(NO))); } - (void)testHasNextPageRaisesExpectionWhenNotLoaded { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; BOOL loaded = NO; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(loaded)] isLoaded]; STAssertThrows([mockPaginator hasNextPage], @"Expected exception due to isLoaded == NO"); } - (void)testHasNextPageRaisesExpectionWhenPageCountIsUnknown { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; BOOL loaded = YES; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(loaded)] isLoaded]; BOOL hasPageCount = NO; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(hasPageCount)] hasPageCount]; STAssertThrows([mockPaginator hasNextPage], @"Expected exception due to pageCount == NSUIntegerMax"); } - (void)testHasPreviousPageRaisesExpectionWhenNotLoaded { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; BOOL loaded = NO; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(loaded)] isLoaded]; STAssertThrows([mockPaginator hasPreviousPage], @"Expected exception due to isLoaded == NO"); } - (void)testKnowledgeOfPreviousPage { RKPaginator *paginator = [RKPaginator new]; id mockPaginator = [OCMockObject partialMockForObject:paginator]; BOOL isLoaded = YES; NSUInteger perPage = 5; NSUInteger pageCount = 3; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(isLoaded)] isLoaded]; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(perPage)] perPage]; [[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(pageCount)] pageCount]; NSUInteger currentPage = 3; [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(YES))); currentPage = 2; [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(YES))); currentPage = 1; [[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage]; assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(NO))); } - (void)testProxyAttributes { RKPaginator *paginator = [RKPaginator new]; [paginator setValue:@(12345) forKey:@"pageCountNumber"]; expect(paginator.pageCount).to.equal(12345); expect([paginator valueForKey:@"pageCountNumber"]).to.equal(12345); [paginator setValue:@(1) forKey:@"currentPageNumber"]; expect(paginator.currentPage).to.equal(1); expect([paginator valueForKey:@"currentPageNumber"]).to.equal(1); [paginator setValue:@(25) forKey:@"objectCountNumber"]; expect(paginator.objectCount).to.equal(25); expect([paginator valueForKey:@"objectCountNumber"]).to.equal(25); [paginator setValue:@(10) forKey:@"perPageNumber"]; expect(paginator.perPage).to.equal(10); expect([paginator valueForKey:@"perPageNumber"]).to.equal(10); } - (void)testPaginatorWithPaginationURLThatIncludesTrailingSlash { NSURL *paginationURL = [NSURL URLWithString:@"/paginate/?per_page=:perPage&page=:currentPage" relativeToURL:[RKTestFactory baseURL]]; NSURLRequest *request = [NSURLRequest requestWithURL:paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; __block NSArray *blockObjects = nil; [paginator setCompletionBlockWithSuccess:^(RKPaginator *paginator, NSArray *objects, NSUInteger page) { blockObjects = objects; } failure:nil]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect(blockObjects).willNot.beNil(); expect(paginator.pageCount).to.equal(0); expect(paginator.objectCount).to.equal(0); } - (void)testOffsetNumberOfNextPage { NSURLRequest *request = [NSURLRequest requestWithURL:self.paginationURL]; RKPaginator *paginator = [[RKPaginator alloc] initWithRequest:request paginationMapping:self.paginationMapping responseDescriptors:@[ self.responseDescriptor ]]; [paginator loadPage:1]; [paginator waitUntilFinished]; expect(paginator.offset).to.equal(0); [paginator loadNextPage]; [paginator waitUntilFinished]; expect(paginator.offset).to.equal(3); } @end