如何使用 RestKit 正确测试映射(嵌套映射,对多)

Posted

技术标签:

【中文标题】如何使用 RestKit 正确测试映射(嵌套映射,对多)【英文标题】:How to correctly test mapping with RestKit (nested mapping, to-many) 【发布时间】:2013-12-05 20:48:51 【问题描述】:

我正在尝试测试我的核心数据模型的响应映射。我在 Core Data 模型中有几个类,但其中 2 个我很想测试。第一类是CTFUser 类,保存用户的基本信息,第二类是CTFCharacter 类。用户可能有几个角色(某种玩家)多对多关系。我想做的是为CTFUser 类和CTFUserCTFCharacter 类之间的关系编写正确的测试。当我编写没有属性characters 的测试时,NSOrderedSet 类型测试通过了。当我尝试测试属性characters 而不检查valueRKPropertyMappingTestExpectation 测试通过。但是当我想检查这个映射的值时,我遇到了崩溃。

测试中使用的 JSON 夹具:


    "username": "tomkowz12",
    "email": "tmk.szlc@gmail.com",
    "password": "password123",
    "nick": "black_lord",
    "location": [10, 20],
    "characters" : [
                    "type": 1,
                    "total_time": 21,
                    "total_score": 100,
                    "health": 100,
                    "level": 20,
                    "is_active": 1
                    ,
                    
                    "type": 2,
                    "total_time": 23,
                    "total_score": 98,
                    "health": 55,
                    "level": 12,
                    "is_active": 0
                    ]

CTFUser 类

@interface CTFUser : CustomManagedObject

@property (nonatomic, retain) NSString * email;
@property (nonatomic, retain) NSString * nick;
@property (nonatomic, retain) NSString * username;
@property (nonatomic, retain) NSString * password;
@property (nonatomic, retain) id location;
@property (nonatomic, retain) CTFGame *game;
@property (nonatomic, retain) NSOrderedSet *characters;
+ (NSDictionary *)dictionaryForResponseMapping;

@end

和具有此类属性映射的字典字典:

+ (NSDictionary *)dictionaryForResponseMapping 
return @@"username" : @"username",
         @"email" : @"email",
         @"password" : @"password",
         @"nick" : @"nick",
         @"location" : @"location";

CTFCharacter 类

@interface CTFCharacter : CustomManagedObject

@property (nonatomic, retain) NSNumber * type;
@property (nonatomic, retain) NSNumber * totalTime;
@property (nonatomic, retain) NSNumber * totalScore;
@property (nonatomic, retain) NSNumber * health;
@property (nonatomic, retain) NSNumber * level;
@property (nonatomic, retain) NSNumber * active;
@property (nonatomic, retain) CTFUser *user;

+ (NSDictionary *)dictionaryResponseMapping;

@end

和带有属性映射的字典:

+ (NSDictionary *)dictionaryResponseMapping 
    return @@"type": @"type",
             @"total_time": @"totalTime",
             @"total_score": @"totalScore",
             @"health": @"health",
             @"level": @"level",
             @"is_active": @"active";

** 单元测试 ** - (void)testUserResponseMApping id parsedJSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"user-response.json"];

    /// Configure mapping
    RKEntityMapping *userMapping =
    [RKEntityMapping mappingForEntityForName:NSStringFromClass([CTFUser class]) inManagedObjectStore:_manager.managedObjectStore];
    [userMapping addAttributeMappingsFromDictionary:[CTFUser dictionaryForResponseMapping]];

    RKEntityMapping *characterMapping =
    [RKEntityMapping mappingForEntityForName:NSStringFromClass([CTFCharacter class]) inManagedObjectStore:_manager.managedObjectStore];
    [characterMapping addAttributeMappingsFromDictionary:[CTFCharacter dictionaryResponseMapping]];

    [userMapping addRelationshipMappingWithSourceKeyPath:@"characters" mapping:characterMapping];

    /// Configure expectations
    RKMappingTest *test = [RKMappingTest testForMapping:userMapping sourceObject:parsedJSON destinationObject:nil];
    test.managedObjectContext = _service.managedObjectContext;

    RKPropertyMappingTestExpectation *usernameExpectation =
    [RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"username" destinationKeyPath:@"username" value:@"tomkowz12"];
    [test addExpectation:usernameExpectation];

    RKPropertyMappingTestExpectation *emailExpectation =
    [RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"email" destinationKeyPath:@"email" value:@"tmk.szlc@gmail.com"];
    [test addExpectation:emailExpectation];

    RKPropertyMappingTestExpectation *passwordExpectation =
    [RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"password" destinationKeyPath:@"password" value:@"password123"];
    [test addExpectation:passwordExpectation];

    RKPropertyMappingTestExpectation *nickExpectation =
    [RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"nick" destinationKeyPath:@"nick" value:@"black_lord"];
    [test addExpectation:nickExpectation];

    RKPropertyMappingTestExpectation *locationExpectation =
    [RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"location" destinationKeyPath:@"location" value:@[@(10), @(20)]];
    [test addExpectation:locationExpectation];

    /// Configure expectation objects
    CTFCharacter *firstCharacter = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([CTFCharacter class]) inManagedObjectContext:_service.managedObjectContext];
    firstCharacter.type = @(1);
    firstCharacter.totalTime = @(21);
    firstCharacter.totalScore = @(100);
    firstCharacter.health = @(100);
    firstCharacter.level = @(20);
    firstCharacter.active = @YES;

    CTFCharacter *secondCharacter = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([CTFCharacter class]) inManagedObjectContext:_service.managedObjectContext];
    secondCharacter.type = @(2);
    secondCharacter.totalTime = @(23);
    secondCharacter.totalScore = @(98);
    secondCharacter.health = @(55);
    secondCharacter.level = @(12);
    secondCharacter.active = @NO;

    NSOrderedSet *set = [[NSOrderedSet alloc] initWithArray:@[firstCharacter, secondCharacter]];
    RKPropertyMappingTestExpectation *charactersExpectation =
    [RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"characters" destinationKeyPath:@"characters" value:set]; /// <-- this won't pass
    // [RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"characters" destinationKeyPath:@"characters"]; /// <-- this pass

    [test addExpectation:charactersExpectation];

    [test verify];

崩溃日志

<unknown>:0: error: -[CTFUserTests testUserResponseMApping] : mapped to unexpected _NSFaultingMutableOrderedSet value 'Relationship 'characters' on managed object (0x8c4fba0) <CTFUser: 0x8c4fba0> (entity: CTFUser; id: 0x8c4d2a0 <x-coredata:///CTFUser/t28A63306-B2D2-41F5-A0C3-1BE85520B04710> ; data: 
characters =     (
    "0x8c52470 <x-coredata:///CTFCharacter/t28A63306-B2D2-41F5-A0C3-1BE85520B04711>",
    "0x8c4d330 <x-coredata:///CTFCharacter/t28A63306-B2D2-41F5-A0C3-1BE85520B04712>"
);
email = "tmk.szlc@gmail.com";
game = nil;
location = "(\n    10,\n    20\n)";
nick = "black_lord";
password = password123;
username = tomkowz12;
) with objects (
<CTFCharacter: 0x8c528f0> (entity: CTFCharacter; id: 0x8c52470 <x-coredata:///CTFCharacter/t28A63306-B2D2-41F5-A0C3-1BE85520B04711> ; data: 
active = 1;
health = 100;
level = 20;
totalScore = 100;
totalTime = 21;
type = 1;
user = "0x8c4d2a0 <x-coredata:///CTFUser/t28A63306-B2D2-41F5-A0C3-1BE85520B04710>";
),
<CTFCharacter: 0x8c4ef60> (entity: CTFCharacter; id: 0x8c4d330 <x-coredata:///CTFCharacter/t28A63306-B2D2-41F5-A0C3-1BE85520B04712> ; data: 
active = 0;
health = 55;
level = 12;
totalScore = 98;
totalTime = 23;
type = 2;
user = "0x8c4d2a0 <x-coredata:///CTFUser/t28A63306-B2D2-41F5-A0C3-1BE85520B04710>";
)
)'
(
0   CoreFoundation                      0x021185e4 __exceptionPreprocess + 180
1   libobjc.A.dylib                     0x01e9b8b6 objc_exception_throw + 44
2   CoreFoundation                      0x021a86a1 -[NSException raise] + 17
3   Capture The FlagTests               0x0a0f1d60 -[RKMappingTest verifyExpectation:] + 464
4   Capture The FlagTests               0x0a0f2054 -[RKMappingTest verify] + 404
5   Capture The FlagTests               0x0a002cf0 -[CTFUserTests testUserResponseMApping] + 3648
6   CoreFoundation                      0x0210cd1d __invoking___ + 29
7   CoreFoundation                      0x0210cc2a -[NSInvocation invoke] + 362
8   XCTest                              0x201032bf -[XCTestCase invokeTest] + 212
9   XCTest                              0x2010338d -[XCTestCase performTest:] + 111
10  XCTest                              0x2010417c -[XCTest run] + 82
11  XCTest                              0x20102a44 -[XCTestSuite performTest:] + 139
12  XCTest                              0x2010417c -[XCTest run] + 82
13  XCTest                              0x20102a44 -[XCTestSuite performTest:] + 139
14  XCTest                              0x2010417c -[XCTest run] + 82
15  XCTest                              0x20102a44 -[XCTestSuite performTest:] + 139
16  XCTest                              0x2010417c -[XCTest run] + 82
17  XCTest                              0x20105aa1 +[XCTestProbe runTests:] + 183
18  Foundation                          0x01ad212c __NSFireDelayedPerform + 372
19  CoreFoundation                      0x020d6bd6 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 22
20  CoreFoundation                      0x020d65bd __CFRunLoopDoTimer + 1181
21  CoreFoundation                      0x020be628 __CFRunLoopRun + 1816
22  CoreFoundation                      0x020bdac3 CFRunLoopRunSpecific + 467
23  CoreFoundation                      0x020bd8db CFRunLoopRunInMode + 123
24  GraphicsServices                    0x03ade9e2 GSEventRunModal + 192
25  GraphicsServices                    0x03ade809 GSEventRun + 104
26  UIKit                               0x00c09d3b UIApplicationMain + 1225
27  Capture The Flag                    0x00008d0d main + 141
28  libdyld.dylib                       0x0275670d start + 1
)

我在哪里做错了?应该如何测试characters 属性的值?其他值正在测试中并通过。

编辑:

当我发出请求时,服务器的响应看起来不错,所有属性和关系都映射正确,但测试问题仍然存在。

- (void)getUser 
CoreDataService *_service = [CoreDataService sharedInstance];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:_service.managedObjectModel];
_connection.manager.managedObjectStore = managedObjectStore;

RKEntityMapping *userMapping =
[RKEntityMapping mappingForEntityForName:NSStringFromClass([CTFUser class]) inManagedObjectStore:managedObjectStore];

[userMapping addAttributeMappingsFromDictionary:[CTFUser dictionaryForResponseMapping]];

RKEntityMapping *characterMapping =
[RKEntityMapping mappingForEntityForName:NSStringFromClass([CTFCharacter class]) inManagedObjectStore:managedObjectStore];
[characterMapping addAttributeMappingsFromDictionary:[CTFCharacter dictionaryResponseMapping]];

[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"characters" toKeyPath:@"characters" withMapping:characterMapping]];

RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping method:RKRequestMethodGET pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

[_connection.manager addResponseDescriptor:descriptor];

[_connection.manager getObject:nil path:@"test" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) 
    CTFUser *fetchedUser = (CTFUser *)mappingResult.firstObject;
    NSLog(@"%@", fetchedUser);
 failure:^(RKObjectRequestOperation *operation, NSError *error) 
    NSLog(@"failure");
];

提前谢谢你。

【问题讨论】:

【参考方案1】:

好的,我做到了。解决方法很简单。昨天很晚,所以我没有看 RestKit 文档。

而不是使用:

expectationWithSourceKeyPath:destinationKeyPath:value:

我用过:

expectationWithSourceKeyPath:destinationKeyPath:evaluationBlock:

修改后的代码如下:

RKPropertyMappingTestExpectation *charactersExpectation =
[RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"characters" destinationKeyPath:@"characters" evaluationBlock:^BOOL(RKPropertyMappingTestExpectation *expectation, RKPropertyMapping *mapping, id mappedValue, NSError *__autoreleasing *error) 

    NSArray *commitedKeysToCompare = @[@"type, totalTime, totalScore, healt, level, active"];

    NSArray *array = [mappedValue allObjects];
    BOOL containsTwoItems = array.count == 2;

    /// Test First character
    CTFCharacter *firstFetchedCharacter = (CTFCharacter *)array[0];

    CTFCharacter *firstReferenceCharacter = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([CTFCharacter class]) inManagedObjectContext:_service.managedObjectContext];
    firstReferenceCharacter.type = @(1);
    firstReferenceCharacter.totalTime = @(21);
    firstReferenceCharacter.totalScore = @(100);
    firstReferenceCharacter.health = @(100);
    firstReferenceCharacter.level = @(20);
    firstReferenceCharacter.active = @YES;

    BOOL isFirstEqual = [[firstFetchedCharacter committedValuesForKeys:commitedKeysToCompare] isEqual:[firstReferenceCharacter committedValuesForKeys:commitedKeysToCompare]];

    /// Test Second character
    CTFCharacter *secondFetchedCharacter = (CTFCharacter *)array[1];

    CTFCharacter *secondReferenceCharacter = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([CTFCharacter class]) inManagedObjectContext:_service.managedObjectContext];
    secondReferenceCharacter.type = @(2);
    secondReferenceCharacter.totalTime = @(23);
    secondReferenceCharacter.totalScore = @(98);
    secondReferenceCharacter.health = @(55);
    secondReferenceCharacter.level = @(12);
    secondReferenceCharacter.active = @NO;

    BOOL isSecondEqual = [[secondFetchedCharacter committedValuesForKeys:commitedKeysToCompare] isEqual:[secondReferenceCharacter committedValuesForKeys:commitedKeysToCompare]];

    return isFirstEqual && isSecondEqual && containsTwoItems;
];

【讨论】:

以上是关于如何使用 RestKit 正确测试映射(嵌套映射,对多)的主要内容,如果未能解决你的问题,请参考以下文章

RestKit 将未嵌套关系映射到拥有对象

Restkit 0.20 嵌套对象数组映射问题

RestKit 0.20:映射复杂的动态嵌套 JSON

如何使用 RestKit 测试这个对象映射

restkit 映射嵌套数组为空(带有 json 响应的嵌套 restkit 请求)

Restkit嵌套对象映射问题