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



我正在尝试测试我的核心数据模型的响应映射。我在核心数据模型中有几个类,但有两个我很感兴趣测试。第一类是保存用户基本信息的CTFUser类,第二类是CTFCharacter类。用户可能有几个角色(某种玩家)到多个关系。我想做的是正确编写CTFUser类的测试以及CTFUserCTFCharacter类之间的关系。当我编写没有属性characters的测试时,它是NSOrderedSet类型测试通过的。当我尝试在不检查RKPropertyMappingTestExpectationvalue的情况下测试属性characters时,测试通过。但当我想检查这个映射的值时,我就崩溃了。

测试中使用的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"};
}

CTC字符类

@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    20n)";
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");
}];
}

提前谢谢。

好的,我做到了。解决问题的方法非常简单。昨天晚了,所以我没有看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;
}];