Files
RestKit/Specs/ObjectMapping/RKObjectLoaderSpec.m

495 lines
22 KiB
Objective-C

//
// RKObjectLoaderSpec.m
// RestKit
//
// Created by Blake Watters on 4/27/11.
// Copyright 2011 Two Toasters
//
// 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 "RKSpecEnvironment.h"
#import "RKObjectMappingProvider.h"
#import "RKErrorMessage.h"
// Models
#import "RKObjectLoaderSpecResultModel.h"
@interface RKSpecComplexUser : NSObject {
NSNumber* _userID;
NSString* _firstname;
NSString* _lastname;
NSString* _email;
NSString* _phone;
}
@property (nonatomic, retain) NSNumber* userID;
@property (nonatomic, retain) NSString* firstname;
@property (nonatomic, retain) NSString* lastname;
@property (nonatomic, retain) NSString* email;
@property (nonatomic, retain) NSString* phone;
@end
@implementation RKSpecComplexUser
@synthesize userID = _userID;
@synthesize firstname = _firstname;
@synthesize lastname = _lastname;
@synthesize phone = _phone;
@synthesize email = _email;
+ (NSDictionary*)elementToPropertyMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
@"id", @"userID",
@"firstname", @"firstname",
@"lastname", @"lastname",
@"email", @"email",
@"phone", @"phone",
nil];
}
- (void)willSendWithObjectLoader:(RKObjectLoader *)objectLoader {
NSLog(@"RKSpecComplexUser willSendWithObjectLoader: INVOKED!!");
return;
}
@end
@interface RKSpecResponseLoaderWithWillMapData : RKSpecResponseLoader {
id _mappableData;
}
@property (nonatomic, readonly) id mappableData;
@end
@implementation RKSpecResponseLoaderWithWillMapData
@synthesize mappableData = _mappableData;
- (void)dealloc {
[_mappableData release];
[super dealloc];
}
- (void)objectLoader:(RKObjectLoader *)loader willMapData:(inout id *)mappableData {
[*mappableData setValue:@"monkey!" forKey:@"newKey"];
_mappableData = [*mappableData retain];
}
@end
/////////////////////////////////////////////////////////////////////////////
@interface RKObjectLoaderSpec : RKSpec {
}
@end
@implementation RKObjectLoaderSpec
- (RKObjectMappingProvider*)providerForComplexUser {
RKObjectMappingProvider* provider = [[RKObjectMappingProvider new] autorelease];
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[RKSpecComplexUser class]];
[userMapping addAttributeMapping:[RKObjectAttributeMapping mappingFromKeyPath:@"firstname" toKeyPath:@"firstname"]];
[provider setMapping:userMapping forKeyPath:@"data.STUser"];
return provider;
}
- (RKObjectMappingProvider*)errorMappingProvider {
RKObjectMappingProvider* provider = [[RKObjectMappingProvider new] autorelease];
RKObjectMapping* errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addAttributeMapping:[RKObjectAttributeMapping mappingFromKeyPath:@"" toKeyPath:@"errorMessage"]];
[provider setMapping:errorMapping forKeyPath:@"error"];
[provider setMapping:errorMapping forKeyPath:@"errors"];
return provider;
}
- (void)itShouldHandleTheErrorCaseAppropriately {
RKObjectManager* objectManager = RKSpecNewObjectManager();
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* objectLoader = [objectManager objectLoaderWithResourcePath:@"/errors.json" delegate:responseLoader];
objectLoader.method = RKRequestMethodGET;
[objectManager setMappingProvider:[self errorMappingProvider]];
[objectLoader sendAsynchronously];
[responseLoader waitForResponse];
[expectThat(responseLoader.failureError) shouldNot:be(nil)];
[expectThat([responseLoader.failureError localizedDescription]) should:be(@"error1, error2")];
NSArray* objects = [[responseLoader.failureError userInfo] objectForKey:RKObjectMapperErrorObjectsKey];
RKErrorMessage* error1 = [objects objectAtIndex:0];
RKErrorMessage* error2 = [objects lastObject];
[expectThat(error1.errorMessage) should:be(@"error1")];
[expectThat(error2.errorMessage) should:be(@"error2")];
}
- (void)itShouldNotCrashWhenLoadingAnErrorResponseWithAnUnmappableMIMEType {
RKObjectManager* objectManager = RKSpecNewObjectManager();
RKSpecStubNetworkAvailability(YES);
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
[objectManager loadObjectsAtResourcePath:@"/404" delegate:loader];
[loader waitForResponse];
assertThatBool(loader.unknownResponse, is(equalToBool(YES)));
}
#pragma mark - Complex JSON
- (void)itShouldLoadAComplexUserObjectWithTargetObject {
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:RKSpecGetBaseURL()];
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* objectLoader = [objectManager objectLoaderWithResourcePath:@"/JSON/ComplexNestedUser.json" delegate:responseLoader];
NSString *authString = [NSString stringWithFormat:@"TRUEREST username=%@&password=%@&apikey=123456&class=iphone", @"username", @"password"];
[objectLoader.URLRequest addValue:authString forHTTPHeaderField:@"Authorization"];
objectLoader.method = RKRequestMethodGET;
objectLoader.targetObject = user;
[objectManager setMappingProvider:[self providerForComplexUser]];
[objectLoader sendAsynchronously];
[responseLoader waitForResponse];
NSLog(@"Response: %@", responseLoader.objects);
[expectThat(user.firstname) should:be(@"Diego")];
}
- (void)itShouldLoadAComplexUserObjectWithoutTargetObject {
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:RKSpecGetBaseURL()];
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* objectLoader = [objectManager objectLoaderWithResourcePath:@"/JSON/ComplexNestedUser.json" delegate:responseLoader];
objectLoader.method = RKRequestMethodGET;
[objectManager setMappingProvider:[self providerForComplexUser]];
[objectLoader sendAsynchronously];
[responseLoader waitForResponse];
[expectThat([responseLoader.objects count]) should:be(1)];
RKSpecComplexUser* user = [responseLoader.objects lastObject];
[expectThat(user.firstname) should:be(@"Diego")];
}
- (void)itShouldLoadAComplexUserObjectUsingRegisteredKeyPath {
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:RKSpecGetBaseURL()];
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* objectLoader = [objectManager objectLoaderWithResourcePath:@"/JSON/ComplexNestedUser.json" delegate:responseLoader];
objectLoader.method = RKRequestMethodGET;
[objectManager setMappingProvider:[self providerForComplexUser]];
[objectLoader sendAsynchronously];
[responseLoader waitForResponse];
[expectThat([responseLoader.objects count]) should:be(1)];
RKSpecComplexUser* user = [responseLoader.objects lastObject];
[expectThat(user.firstname) should:be(@"Diego")];
}
#pragma mark - willSendWithObjectLoader:
- (void)itShouldInvokeWillSendWithObjectLoaderOnSend {
// RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:RKSpecGetBaseURL()];
RKObjectManager* objectManager = RKSpecNewObjectManager();
[objectManager setMappingProvider:[self providerForComplexUser]];
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
id mockObject = [OCMockObject partialMockForObject:user];
// Explicitly init so we don't get a managed object loader...
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* objectLoader = [[RKObjectLoader alloc] initWithResourcePath:@"/" objectManager:objectManager delegate:responseLoader];
objectLoader.sourceObject = mockObject;
[[mockObject expect] willSendWithObjectLoader:objectLoader];
[objectLoader send];
[responseLoader waitForResponse];
[mockObject verify];
}
- (void)itShouldInvokeWillSendWithObjectLoaderOnSendAsynchronously {
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:RKSpecGetBaseURL()];
[objectManager setMappingProvider:[self providerForComplexUser]];
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
id mockObject = [OCMockObject partialMockForObject:user];
// Explicitly init so we don't get a managed object loader...
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* objectLoader = [[RKObjectLoader alloc] initWithResourcePath:@"/" objectManager:objectManager delegate:responseLoader];
objectLoader.sourceObject = mockObject;
[[mockObject expect] willSendWithObjectLoader:objectLoader];
[objectLoader sendAsynchronously];
[responseLoader waitForResponse];
[mockObject verify];
}
- (void)itShouldInvokeWillSendWithObjectLoaderOnSendSynchronously {
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:RKSpecGetBaseURL()];
[objectManager setMappingProvider:[self providerForComplexUser]];
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
id mockObject = [OCMockObject partialMockForObject:user];
// Explicitly init so we don't get a managed object loader...
RKObjectLoader* objectLoader = [[RKObjectLoader alloc] initWithResourcePath:@"/" objectManager:objectManager delegate:nil];
objectLoader.sourceObject = mockObject;
[[mockObject expect] willSendWithObjectLoader:objectLoader];
[objectLoader sendSynchronously];
[mockObject verify];
}
- (void)itShouldLoadResultsNestedAtAKeyPath {
RKObjectManager* objectManager = RKSpecNewObjectManager();
RKObjectMapping* objectMapping = [RKObjectMapping mappingForClass:[RKObjectLoaderSpecResultModel class]];
[objectMapping mapKeyPath:@"id" toAttribute:@"ID"];
[objectMapping mapKeyPath:@"ends_at" toAttribute:@"endsAt"];
[objectMapping mapKeyPath:@"photo_url" toAttribute:@"photoURL"];
[objectManager.mappingProvider setMapping:objectMapping forKeyPath:@"results"];
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
[objectManager loadObjectsAtResourcePath:@"/JSON/ArrayOfResults.json" delegate:loader];
[loader waitForResponse];
assertThat([loader objects], hasCountOf(2));
assertThat([[[loader objects] objectAtIndex:0] ID], is(equalToInt(226)));
assertThat([[[loader objects] objectAtIndex:0] photoURL], is(equalTo(@"1308262872.jpg")));
assertThat([[[loader objects] objectAtIndex:1] ID], is(equalToInt(235)));
assertThat([[[loader objects] objectAtIndex:1] photoURL], is(equalTo(@"1308634984.jpg")));
}
- (void)itShouldAllowMutationOfTheParsedDataInWillMapData {
RKSpecResponseLoaderWithWillMapData* loader = (RKSpecResponseLoaderWithWillMapData*)[RKSpecResponseLoaderWithWillMapData responseLoader];
RKObjectManager* manager = RKSpecNewObjectManager();
RKSpecStubNetworkAvailability(YES);
[manager loadObjectsAtResourcePath:@"/JSON/humans/1.json" delegate:loader];
[loader waitForResponse];
assertThat([loader.mappableData valueForKey:@"newKey"], is(equalTo(@"monkey!")));
}
- (void)itShouldAllowYouToPostAnObjectAndHandleAnEmpty204Response {
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[RKSpecComplexUser class]];
[mapping mapAttributes:@"firstname", @"lastname", @"email", nil];
RKObjectMapping* serializationMapping = [mapping inverseMapping];
RKObjectManager* objectManager = RKSpecNewObjectManager();
[objectManager.router routeClass:[RKSpecComplexUser class] toResourcePath:@"/204"];
[objectManager.mappingProvider setSerializationMapping:serializationMapping forClass:[RKSpecComplexUser class]];
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
user.firstname = @"Blake";
user.lastname = @"Watters";
user.email = @"blake@restkit.org";
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* loader = [objectManager objectLoaderForObject:user method:RKRequestMethodPOST delegate:responseLoader];
loader.objectMapping = mapping;
[loader send];
[responseLoader waitForResponse];
assertThatBool([responseLoader success], is(equalToBool(YES)));
assertThat(user.email, is(equalTo(@"blake@restkit.org")));
}
- (void)itShouldAllowYouToPOSTAnObjectAndMapBackNonNestedContent {
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[RKSpecComplexUser class]];
[mapping mapAttributes:@"firstname", @"lastname", @"email", nil];
RKObjectMapping* serializationMapping = [mapping inverseMapping];
RKObjectManager* objectManager = RKSpecNewObjectManager();
[objectManager.router routeClass:[RKSpecComplexUser class] toResourcePath:@"/notNestedUser"];
[objectManager.mappingProvider setSerializationMapping:serializationMapping forClass:[RKSpecComplexUser class]];
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
user.firstname = @"Blake";
user.lastname = @"Watters";
user.email = @"blake@restkit.org";
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* loader = [objectManager objectLoaderForObject:user method:RKRequestMethodPOST delegate:responseLoader];
loader.objectMapping = mapping;
[loader send];
[responseLoader waitForResponse];
assertThatBool([responseLoader success], is(equalToBool(YES)));
assertThat(user.email, is(equalTo(@"changed")));
}
- (void)itShouldAllowYouToPOSTAnObjectOfOneTypeAndGetBackAnother {
RKObjectMapping* sourceMapping = [RKObjectMapping mappingForClass:[RKSpecComplexUser class]];
[sourceMapping mapAttributes:@"firstname", @"lastname", @"email", nil];
RKObjectMapping* serializationMapping = [sourceMapping inverseMapping];
RKObjectMapping* targetMapping = [RKObjectMapping mappingForClass:[RKObjectLoaderSpecResultModel class]];
[targetMapping mapAttributes:@"ID", nil];
RKObjectManager* objectManager = RKSpecNewObjectManager();
[objectManager.router routeClass:[RKSpecComplexUser class] toResourcePath:@"/notNestedUser"];
[objectManager.mappingProvider setSerializationMapping:serializationMapping forClass:[RKSpecComplexUser class]];
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
user.firstname = @"Blake";
user.lastname = @"Watters";
user.email = @"blake@restkit.org";
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* loader = [objectManager objectLoaderForObject:user method:RKRequestMethodPOST delegate:responseLoader];
loader.sourceObject = user;
loader.targetObject = nil;
loader.objectMapping = targetMapping;
[loader send];
[responseLoader waitForResponse];
assertThatBool([responseLoader success], is(equalToBool(YES)));
// Our original object should not have changed
assertThat(user.email, is(equalTo(@"blake@restkit.org")));
// And we should have a new one
RKObjectLoaderSpecResultModel* newObject = [[responseLoader objects] lastObject];
assertThat(newObject, is(instanceOf([RKObjectLoaderSpecResultModel class])));
assertThat(newObject.ID, is(equalToInt(31337)));
}
// TODO: Should live in a different file...
- (void)itShouldAllowYouToPOSTAnObjectAndMapBackNonNestedContentViapostObject {
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[RKSpecComplexUser class]];
[mapping mapAttributes:@"firstname", @"lastname", @"email", nil];
RKObjectMapping* serializationMapping = [mapping inverseMapping];
RKObjectManager* objectManager = RKSpecNewObjectManager();
[objectManager.router routeClass:[RKSpecComplexUser class] toResourcePath:@"/notNestedUser"];
[objectManager.mappingProvider setSerializationMapping:serializationMapping forClass:[RKSpecComplexUser class]];
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
user.firstname = @"Blake";
user.lastname = @"Watters";
user.email = @"blake@restkit.org";
// NOTE: The postObject: should infer the target object from sourceObject and the mapping class
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
[objectManager postObject:user mapResponseWith:mapping delegate:responseLoader];
[responseLoader waitForResponse];
assertThatBool([responseLoader success], is(equalToBool(YES)));
assertThat(user.email, is(equalTo(@"changed")));
}
- (void)itShouldRespectTheRootKeyPathWhenConstructingATemporaryObjectMappingProvider {
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[RKSpecComplexUser class]];
userMapping.rootKeyPath = @"data.STUser";
[userMapping mapAttributes:@"firstname", nil];
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:RKSpecGetBaseURL()];
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* objectLoader = [objectManager objectLoaderWithResourcePath:@"/JSON/ComplexNestedUser.json" delegate:responseLoader];
objectLoader.objectMapping = userMapping;
objectLoader.method = RKRequestMethodGET;
objectLoader.targetObject = user;
[objectLoader sendAsynchronously];
[responseLoader waitForResponse];
NSLog(@"Response: %@", responseLoader.objects);
[expectThat(user.firstname) should:be(@"Diego")];
}
- (void)itShouldReturnSuccessWhenTheStatusCodeIs200AndTheResponseBodyIsEmpty {
RKObjectManager* objectManager = RKSpecNewObjectManager();
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
user.firstname = @"Blake";
user.lastname = @"Watters";
user.email = @"blake@restkit.org";
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[RKSpecComplexUser class]];
userMapping.rootKeyPath = @"data.STUser";
[userMapping mapAttributes:@"firstname", nil];
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* objectLoader = [RKObjectLoader loaderWithResourcePath:@"/humans/1234" objectManager:objectManager delegate:responseLoader];
objectLoader.method = RKRequestMethodDELETE;
objectLoader.objectMapping = userMapping;
objectLoader.targetObject = user;
[objectLoader send];
[responseLoader waitForResponse];
assertThatBool(responseLoader.success, is(equalToBool(YES)));
}
- (void)itShouldInvokeTheDelegateWithTheTargetObjectWhenTheStatusCodeIs200AndTheResponseBodyIsEmpty {
RKObjectManager* objectManager = RKSpecNewObjectManager();
RKSpecComplexUser* user = [[RKSpecComplexUser new] autorelease];
user.firstname = @"Blake";
user.lastname = @"Watters";
user.email = @"blake@restkit.org";
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[RKSpecComplexUser class]];
userMapping.rootKeyPath = @"data.STUser";
[userMapping mapAttributes:@"firstname", nil];
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
RKObjectLoader* objectLoader = [RKObjectLoader loaderWithResourcePath:@"/humans/1234" objectManager:objectManager delegate:responseLoader];
objectLoader.method = RKRequestMethodDELETE;
objectLoader.objectMapping = userMapping;
objectLoader.targetObject = user;
[objectLoader send];
[responseLoader waitForResponse];
assertThat(responseLoader.objects, hasItem(user));
}
- (void)itShouldConsiderTheLoadOfEmptyObjectsWithoutAnyMappableAttributesAsSuccess {
RKObjectManager* objectManager = RKSpecNewObjectManager();
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[RKSpecComplexUser class]];
[userMapping mapAttributes:@"firstname", nil];
[objectManager.mappingProvider setMapping:userMapping forKeyPath:@"firstUser"];
[objectManager.mappingProvider setMapping:userMapping forKeyPath:@"secondUser"];
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
[objectManager loadObjectsAtResourcePath:@"/users/empty" delegate:responseLoader];
[responseLoader waitForResponse];
assertThatBool(responseLoader.success, is(equalToBool(YES)));
}
- (void)itShouldInvokeTheDelegateOnSuccessIfTheResponseIsAnEmptyArray {
RKObjectManager* objectManager = RKSpecNewObjectManager();
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
responseLoader.timeout = 20;
[objectManager loadObjectsAtResourcePath:@"/empty/array" delegate:responseLoader];
[responseLoader waitForResponse];
assertThat(responseLoader.objects, isNot(nilValue()));
assertThatBool([responseLoader.objects isKindOfClass:[NSArray class]], is(equalToBool(YES)));
assertThat(responseLoader.objects, is(empty()));
}
- (void)itShouldInvokeTheDelegateOnSuccessIfTheResponseIsAnEmptyDictionary {
RKObjectManager* objectManager = RKSpecNewObjectManager();
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
responseLoader.timeout = 20;
[objectManager loadObjectsAtResourcePath:@"/empty/dictionary" delegate:responseLoader];
[responseLoader waitForResponse];
assertThat(responseLoader.objects, isNot(nilValue()));
assertThatBool([responseLoader.objects isKindOfClass:[NSArray class]], is(equalToBool(YES)));
assertThat(responseLoader.objects, is(empty()));
}
- (void)itShouldInvokeTheDelegateOnSuccessIfTheResponseIsAnEmptyString {
RKObjectManager* objectManager = RKSpecNewObjectManager();
RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader];
responseLoader.timeout = 20;
[objectManager loadObjectsAtResourcePath:@"/empty/string" delegate:responseLoader];
[responseLoader waitForResponse];
assertThat(responseLoader.objects, isNot(nilValue()));
assertThatBool([responseLoader.objects isKindOfClass:[NSArray class]], is(equalToBool(YES)));
assertThat(responseLoader.objects, is(empty()));
}
@end