1

I'm using RestKit ~> 0.20.3 and RestKit/Testing ~> 0.20.3. So this is an example of my mapping:

RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[PlayerVO class]];
[mapping addAttributeMappingsFromArray:@[@"firstName", @"middeName", @"lastName", @"dob", @"sex"]];

This is my mock data:

NSDictionary *data = @{@"players": @[@{@"firstName": @"Ahmed", @"middleName": @"Ahmed", @"lastName": @"Ahmed", @"dob": @"100", @"sex": @"m"}]};

This is my mappingTest:

RKMappingTest *mappingTest = [RKMappingTest testForMapping:mapping sourceObject:data destinationObject:nil];

And finally my expectation:

RKPropertyMappingTestExpectation *expectation = [RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"players.firstName" destinationKeyPath:@"firstName" evaluationBlock:^BOOL(RKPropertyMappingTestExpectation *expectation, RKPropertyMapping *mapping, id mappedValue, NSError *__autoreleasing *error) {
    BOOL expect = [mappedValue length] > 0;

    XCTAssertTrue(expect);

    return expect;
}];

[mappingTest addExpectation:expectation];

XCTAssertTrue([mappingTest evaluate]);
XCTAssertNoThrow([mappingTest verify]);

So this test fails as there doesn't seem to be a way I can specify a key path to follow as the data is an array. This is the error I get:

testPlayerServiceGetPlayers] : (([mappingTest verify]) does not throw) failed: throwing "0x8e7d770: failed with error: (null)
RKMappingTest Expectations: (
    "map 'players.firstName' to 'firstName' satisfying evaluation block"
)
Events: (
) during mapping from {
    players =     (
                {
            dob = 100;
            firstName = Ahmed;
            lastName = Ahmed;
            middleName = Ahmed;
            sex = m;
        }
    );
} to (null) with mapping <RKObjectMapping:0x8e6aad0 objectClass=PlayerVO propertyMappings=(
    "<RKAttributeMapping: 0x8e6b880 firstName => firstName>",
    "<RKAttributeMapping: 0x8e61df0 middeName => middeName>",
    "<RKAttributeMapping: 0x8e2f580 lastName => lastName>",
    "<RKAttributeMapping: 0x8e67a60 dob => dob>",
    "<RKAttributeMapping: 0x8e2c120 sex => sex>"
)>"

If I change my data to:

NSDictionary *data = @{@"firstName": @"Ahmed", @"middleName": @"Naseer", @"lastName": @"Nuaman", @"dob": @"524534400", @"sex": @"m"};

And the expectation's expectationWithSourceKeyPath to @"firstName", the tests pass. So this leads me to believe the issue is clearly relative to setting the key path. Now in my app that's done using:

RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method:RKRequestMethodAny pathPattern:nil keyPath:@"players" statusCodes:statusCodes];

However I can't find a way of setting the keyPath for a RKMappingTest or RKPropertyMappingTestExpectation, any ideas on how I can achieve this?

Update

So I had a look throughRKMappingTest.h and found the rootKeyPath. I set the following:

mappingTest.rootKeyPath = @"players";

And still was having problems regarding the mappedValue in the RKPropertyMappingTestExpectation. So I also changed my data to this:

NSDictionary *data = @{@"players": @{@"firstName": @"Ahmed", @"middleName": @"Ahmed", @"lastName": @"Ahmed", @"dob": @"100", @"sex": @"m"}};

And now I can see that mappedValue is now set to Ahmed and updated the RKPropertyMappingTestExpectation to the following:

RKPropertyMappingTestExpectation *expectation = [RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"firstName" destinationKeyPath:@"firstName" evaluationBlock:^BOOL(RKPropertyMappingTestExpectation *expectation, RKPropertyMapping *mapping, id mappedValue, NSError *__autoreleasing *error) {
    return [mappedValue isEqualToString:@"Ahmed"];
}];

But this is using an object rather than an array. Any more suggestions?

Ahmed Nuaman
  • 12,662
  • 15
  • 55
  • 87

2 Answers2

1

I think I've found a somewhat better answer than the reply "you're testing wrong". You should be able to test properly by wrapping your mapping under test in another mapping.

// Data (in your case, you've used a hard coded string here)
id fixtureData = [RKTestFixture parsedObjectWithContentsOfFixture:@"fixture.json"];

// Setup
RKObjectMapping* wrapperMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
RKObjectMapping *mappingToTest = [RKObjectMapping mappingForClass:[PlayerVO class]];
[mappingToTest addAttributeMappingsFromArray:@[@"firstName", @"middeName", @"lastName", @"dob", @"sex"]];
[wrapperMapping addRelationshipMappingWithSourceKeyPath:@"players" mapping:mappingToTest];
RKMappingTest* test = [RKMappingTest testForMapping:wrapperMapping sourceObject:fixtureData destinationObject:nil];

// If you're dealing with a managed object mapping, you'll need these:
// I usually set up a single store in the "+(void)setUp" method and reset it
// on each test in the "-(void)setUp" method. (note one method is class, one
// is instance).
//test.managedObjectContext = managedObjectStore.persistentStoreManagedObjectContext;
//test.mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext cache:[RKFetchRequestManagedObjectCache new]];

// Expectations
[test addExpectation:[RKPropertyMappingTestExpectation expectationWithSourceKeyPath:@"..." destinationKeyPath:@"..."]];
// Add others as you see fit (these will work for the mappingToTest mapping).

// Evaluate
XCTAssert([test evaluate]);
NSDictionary* result = test.destinationObject;
// You can verify counts on the result and any other objects.
// If you have variables in other structures you want tracked, you can expand
// on the wrapper mapping. i.e. If your data contained a "teamName" at the
// same level as your array of "players", you could add a mapping from
// "teamName" to an arbitrary keyPath for your dictionary then access it in the
// result variable.

This has worked for me. I think it's short sighted to say that you shouldn't test collection mappings. You may have a poorly designed REST API that necessitates this, or a complex interaction between managed objects that you want to verify is set up correctly when mapping takes place.

Sandy Chapman
  • 11,133
  • 3
  • 58
  • 67
  • Your code doesn't actually use `wrapperMapping` after it's defined. I guess that's a typo on both lines starting `[mapping add...` where the first should use `mappingToTest` and the second `wrapperMapping`? – Wain May 07 '14 at 15:23
  • @Wain You're quite right. I was transcribing from some of my code which used domain specific variable names. I guess I did a poor job translating them over. I think it should be correct now. – Sandy Chapman May 07 '14 at 16:22
0

You're thinking about testing, and more specifically the scope of testing, wrongly. This is a unit test, and the unit is the mapping. The mapping deals with individual items and that is why your modified test works - because the scope is correct.

Response descriptors have a different scope. You can't test the scope of a response descriptor in a mapping test.

Wain
  • 118,658
  • 15
  • 128
  • 151
  • 1
    Hmm, that doesn't seem right. Why can't I test mapping via a mocked representative response from the server? Since RestKit is supposed to be a toolkit to make calls to the server I want to make sure that the data I'm sending back and the way I'm mapping and handling it is correct. – Ahmed Nuaman Feb 01 '14 at 16:42
  • But your key path is not part of the mapping so it doesn't form part of a mapping test. – Wain Feb 01 '14 at 17:07
  • In your case not so much. But if you have nested mappings and some kind of transformations then it starts to add value. – Wain Feb 01 '14 at 17:20
  • I guess I have to concede. – Ahmed Nuaman Feb 01 '14 at 17:23
  • The problem with limiting the testing of mappings to the scope of a single JSON object is that sometimes the mapping includes data outside of its JSON block using the "@parent" attribute prefix for reference JSON in the parent context. In such a situation, you need the outer context. I absolutely see the value in testing the mappings in the context of fixtures as returned by the server (as we aren't all given ideal REST APIs to interact with and occasionally we need one-off mappings defined for a specific REST call). – Sandy Chapman May 07 '14 at 12:55