Introduce RestKit unit testing classes

This commit is contained in:
Blake Watters
2012-02-17 20:16:31 -05:00
parent 769d4cbc0c
commit 9e50b8cd4f
8 changed files with 726 additions and 4 deletions

View File

@@ -0,0 +1,109 @@
//
// RKMappingTest.h
// RKGithub
//
// Created by Blake Watters on 2/17/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RKObjectMappingOperation.h"
#import "RKMappingTestExpectation.h"
/**
An RKMappingTest object provides support for unit testing
a RestKit object mapping operation by evaluation expectations
against events recorded during an object mapping operation.
*/
@interface RKMappingTest : NSObject <RKObjectMappingOperationDelegate>
///-----------------------------------------------------------------------------
/// @name Creating Tests
///-----------------------------------------------------------------------------
/**
Creates and returns a new test for a given object mapping and source object.
@param mapping The object mapping being tested.
@param sourceObject The source object being mapped.
@return A new mapping test object for a mapping and sourceObject.
*/
+ (RKMappingTest *)testForMapping:(RKObjectMapping *)mapping object:(id)sourceObject;
/**
Creates and returns a new test for a given object mapping, source object and destination
object.
@param mapping The object mapping being tested.
@param sourceObject The source object being mapped from.
@param destinationObject The destionation object being to.
@return A new mapping test object for a mapping, a sourceObject and a destination object.
*/
+ (RKMappingTest *)testForMapping:(RKObjectMapping *)mapping sourceObject:(id)sourceObject destinationObject:(id)destinationObject;
///-----------------------------------------------------------------------------
/// @name Setting Expectations
///-----------------------------------------------------------------------------
/**
Creates and adds an expectation that a key path on the source object will be mapped to a new
key path on the destination object.
@param sourceKeyPath A key path on the sourceObject that should be mapped from.
@param destinationKeyPath A key path on the destinationObject that should be mapped to.
@see RKObjectMappingTestExpectation
*/
- (void)expectMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath;
/**
Creates and adds an expectation that a key path on the source object will be mapped to a new
key path on the destination object with a given value.
@param sourceKeyPath A key path on the sourceObject that should be mapped from.
@param destinationKeyPath A key path on the destinationObject that should be mapped from.
@param value A value that is expected to be assigned to destinationKeyPath on the destinationObject.
@see RKObjectMappingTestExpectation
*/
- (void)expectMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath withValue:(id)value;
/**
Creates and adds an expectation that a key path on the source object will be mapped to a new
key path on the destination object with a value that passes a given test block.
@param sourceKeyPath A key path on the sourceObject that should be mapped from.
@param destinationKeyPath A key path on the destinationObject that should be mapped to.
@param evaluationBlock A block with which to evaluate the success of the mapping.
@see RKObjectMappingTestExpectation
*/
- (void)expectMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath passingTest:(BOOL (^)(RKObjectAttributeMapping *mapping, id value))evaluationBlock;
/**
Adds an expectation to the test to be evaluated during verification.
@param expectation An expectation object to evaluate during test verification.
@see RKObjectMappingTestExpectation
*/
- (void)addExpectation:(RKMappingTestExpectation *)expectation;
///-----------------------------------------------------------------------------
/// @name Verifying Results
///-----------------------------------------------------------------------------
/**
Verifies that the mapping is configured correctly by performing an object mapping operation
and ensuring that all expectations are met.
@exception NSInternalInconsistencyException Raises an
NSInternalInconsistencyException if mapping failes or any expectation is not satisfied.
*/
- (void)verify;
///-----------------------------------------------------------------------------
/// @name Test Configuration
///-----------------------------------------------------------------------------
@property (nonatomic, strong, readonly) RKObjectMapping *mapping;
@property (nonatomic, strong, readonly) id sourceObject;
@property (nonatomic, strong, readonly) id destinationObject;
@end

View File

@@ -0,0 +1,169 @@
//
// RKMappingTest.m
// RKGithub
//
// Created by Blake Watters on 2/17/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import "RKMappingTest.h"
BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue);
///-----------------------------------------------------------------------------
///-----------------------------------------------------------------------------
@interface RKMappingTestEvent : NSObject
@property (nonatomic, strong, readonly) RKObjectAttributeMapping *mapping;
@property (nonatomic, strong, readonly) id value;
@property (nonatomic, readonly) NSString *sourceKeyPath;
@property (nonatomic, readonly) NSString *destinationKeyPath;
+ (RKMappingTestEvent *)eventWithMapping:(RKObjectAttributeMapping *)mapping value:(id)value;
@end
@interface RKMappingTestEvent ()
@property (nonatomic, strong, readwrite) id value;
@property (nonatomic, strong, readwrite) RKObjectAttributeMapping *mapping;
@end
@implementation RKMappingTestEvent
@synthesize value;
@synthesize mapping;
+ (RKMappingTestEvent *)eventWithMapping:(RKObjectAttributeMapping *)mapping value:(id)value {
RKMappingTestEvent *event = [RKMappingTestEvent new];
event.value = value;
event.mapping = mapping;
return event;
}
- (NSString *)sourceKeyPath {
return self.mapping.sourceKeyPath;
}
- (NSString *)destinationKeyPath {
return self.mapping.destinationKeyPath;
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@: mapped sourceKeyPath '%@' => destinationKeyPath '%@' with value: %@", [self class], self.sourceKeyPath, self.destinationKeyPath, self.value];
}
@end
///-----------------------------------------------------------------------------
///-----------------------------------------------------------------------------
@interface RKMappingTest ()
@property (nonatomic, strong, readwrite) RKObjectMapping *mapping;
@property (nonatomic, strong, readwrite) id sourceObject;
@property (nonatomic, strong, readwrite) id destinationObject;
@property (nonatomic, strong) NSMutableArray *expectations;
@property (nonatomic, strong) NSMutableArray *events;
@end
@implementation RKMappingTest
@synthesize sourceObject;
@synthesize destinationObject;
@synthesize mapping;
@synthesize expectations;
@synthesize events;
+ (RKMappingTest *)testForMapping:(RKObjectMapping *)mapping object:(id)sourceObject {
return [[self alloc] initWithMapping:mapping sourceObject:sourceObject destinationObject:nil];
}
+ (RKMappingTest *)testForMapping:(RKObjectMapping *)mapping sourceObject:(id)sourceObject destinationObject:(id)destinationObject {
return [[self alloc] initWithMapping:mapping sourceObject:sourceObject destinationObject:destinationObject];
}
- (id)initWithMapping:(RKObjectMapping *)_mapping sourceObject:(id)_sourceObject destinationObject:(id)_destinationObject {
self = [super init];
if (self) {
self.sourceObject = _sourceObject;
self.destinationObject = _destinationObject;
self.mapping = _mapping;
self.expectations = [NSMutableArray new];
self.events = [NSMutableArray new];
}
return self;
}
- (void)addExpectation:(RKMappingTestExpectation *)expectation {
[self.expectations addObject:expectation];
}
- (void)expectMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath {
[self addExpectation:[RKMappingTestExpectation expectationWithSourceKeyPath:sourceKeyPath destinationKeyPath:destinationKeyPath]];
}
- (void)expectMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath withValue:(id)value {
[self addExpectation:[RKMappingTestExpectation expectationWithSourceKeyPath:sourceKeyPath destinationKeyPath:destinationKeyPath value:value]];
}
- (void)expectMappingFromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath passingTest:(BOOL (^)(RKObjectAttributeMapping *mapping, id value))evaluationBlock {
[self addExpectation:[RKMappingTestExpectation expectationWithSourceKeyPath:sourceKeyPath destinationKeyPath:destinationKeyPath evaluationBlock:evaluationBlock]];
}
- (RKMappingTestEvent *)eventSatisfyingExpectation:(RKMappingTestExpectation *)expectation {
for (RKMappingTestEvent *event in self.events) {
if ([event.sourceKeyPath isEqualToString:expectation.sourceKeyPath] && [event.destinationKeyPath isEqualToString:expectation.destinationKeyPath]) {
if (expectation.evaluationBlock) {
// Let the expectation block evaluate the match
if (expectation.evaluationBlock(event.mapping, event.value)) {
return event;
}
} else if (expectation.value) {
// Use RestKit comparison magic to match values
if (RKObjectIsValueEqualToValue(event.value, expectation.value)) {
return event;
}
} else {
// We only wanted to know that a mapping occured between the keyPaths
return event;
}
}
}
return nil;
}
- (void)verify {
id destination = self.destinationObject ? self.destinationObject : [self.mapping mappableObjectForData:self.sourceObject];
RKObjectMappingOperation *mappingOperation = [RKObjectMappingOperation mappingOperationFromObject:self.sourceObject toObject:destination withMapping:self.mapping];
NSError *error = nil;
mappingOperation.delegate = self;
BOOL success = [mappingOperation performMapping:&error];
if (! success) {
[NSException raise:NSInternalInconsistencyException format:@"%@: failure when mapping from %@ to %@ with mapping %@",
[self description], self.sourceObject, self.destinationObject, self.mapping];
}
for (RKMappingTestExpectation *expectation in self.expectations) {
RKMappingTestEvent *event = [self eventSatisfyingExpectation:expectation];
if (! event) {
[NSException raise:NSInternalInconsistencyException format:@"%@: expectation not satisfied: %@",
[self description], expectation];
}
}
}
#pragma mark - RKObjecMappingOperationDelegate
- (void)addEvent:(RKMappingTestEvent *)event {
[self.events addObject:event];
}
- (void)objectMappingOperation:(RKObjectMappingOperation *)operation didSetValue:(id)value forKeyPath:(NSString *)keyPath usingMapping:(RKObjectAttributeMapping*)_mapping {
[self addEvent:[RKMappingTestEvent eventWithMapping:_mapping value:value]];
}
@end

View File

@@ -0,0 +1,80 @@
//
// RKMappingTestExpectation.h
// RestKit
//
// Created by Blake Watters on 2/17/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import "RKObjectAttributeMapping.h"
/**
An RKMappingTestExpectation defines an expected mapping event that should
occur during the execution of a RKMappingTest.
@see RKMappingTest
*/
@interface RKMappingTestExpectation : NSObject
///-----------------------------------------------------------------------------
/// @name Creating Expectations
///-----------------------------------------------------------------------------
/**
Creates and returns a new expectation specifying that a key path in a source object should be
mapped to another key path on a destination object. The value mapped is not evaluated.
@param sourceKeyPath A key path on the source object that should be mapped.
@param destinationKeyPath A key path on the destination object that should be mapped onto.
@return An expectation specifying that sourceKeyPath should be mapped to destionationKeyPath.
*/
+ (RKMappingTestExpectation *)expectationWithSourceKeyPath:(NSString *)sourceKeyPath destinationKeyPath:(NSString *)destinationKeyPath;
/**
Creates and returns a new expectation specifying that a key path in a source object should be
mapped to another key path on a destination object with a given value.
@param sourceKeyPath A key path on the source object that should be mapped.
@param destinationKeyPath A key path on the destination object that should be mapped onto.
@param value The value that is expected to be assigned to the destination object at destinationKeyPath.
@return An expectation specifying that sourceKeyPath should be mapped to destionationKeyPath with value.
*/
+ (RKMappingTestExpectation *)expectationWithSourceKeyPath:(NSString *)sourceKeyPath destinationKeyPath:(NSString *)destinationKeyPath value:(id)value;
/**
Creates and returns a new expectation specifying that a key path in a source object should be
mapped to another key path on a destinaton object and that the attribute mapping and value should
evaluate to true with a given block.
@param sourceKeyPath A key path on the source object that should be mapped.
@param destinationKeyPath A key path on the destination object that should be mapped onto.
@param evaluationBlock A block with which to evaluate the success of the mapping.
@return An expectation specifying that sourceKeyPath should be mapped to destionationKeyPath with value.
*/
+ (RKMappingTestExpectation *)expectationWithSourceKeyPath:(NSString *)sourceKeyPath destinationKeyPath:(NSString *)destinationKeyPath evaluationBlock:(BOOL (^)(RKObjectAttributeMapping *mapping, id value))evaluationBlock;
///-----------------------------------------------------------------------------
/// @name Expectation Values
///-----------------------------------------------------------------------------
/**
Returns a keyPath on the source object that a value should be mapped from.
*/
@property (nonatomic, copy, readonly) NSString *sourceKeyPath;
/**
Returns a keyPath on the destination object that a value should be mapped to.
*/
@property (nonatomic, copy, readonly) NSString *destinationKeyPath;
/**
Returns the expected value that should be set to the destinationKeyPath of the destination object.
*/
@property (nonatomic, strong, readonly) id value;
/**
A block used to evaluate if the expectation has been satisfied.
*/
@property (nonatomic, copy, readonly) BOOL (^evaluationBlock)(RKObjectAttributeMapping *mapping, id value);
@end

View File

@@ -0,0 +1,62 @@
//
// RKMappingTestExpectation.m
// RestKit
//
// Created by Blake Watters on 2/17/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import "RKMappingTestExpectation.h"
@interface RKMappingTestExpectation ()
@property (nonatomic, copy, readwrite) NSString *sourceKeyPath;
@property (nonatomic, copy, readwrite) NSString *destinationKeyPath;
@property (nonatomic, strong, readwrite) id value;
@property (nonatomic, copy, readwrite) BOOL (^evaluationBlock)(RKObjectAttributeMapping *mapping, id value);
@end
@implementation RKMappingTestExpectation
@synthesize sourceKeyPath;
@synthesize destinationKeyPath;
@synthesize value;
@synthesize evaluationBlock;
+ (RKMappingTestExpectation *)expectationWithSourceKeyPath:(NSString *)sourceKeyPath destinationKeyPath:(NSString *)destinationKeyPath {
RKMappingTestExpectation *expectation = [self new];
expectation.sourceKeyPath = sourceKeyPath;
expectation.destinationKeyPath = destinationKeyPath;
return expectation;
}
+ (RKMappingTestExpectation *)expectationWithSourceKeyPath:(NSString *)sourceKeyPath destinationKeyPath:(NSString *)destinationKeyPath value:(id)value {
RKMappingTestExpectation *expectation = [self new];
expectation.sourceKeyPath = sourceKeyPath;
expectation.destinationKeyPath = destinationKeyPath;
expectation.value = value;
return expectation;
}
+ (RKMappingTestExpectation *)expectationWithSourceKeyPath:(NSString *)sourceKeyPath destinationKeyPath:(NSString *)destinationKeyPath evaluationBlock:(BOOL (^)(RKObjectAttributeMapping *mapping, id value))testBlock {
RKMappingTestExpectation *expectation = [self new];
expectation.sourceKeyPath = sourceKeyPath;
expectation.destinationKeyPath = destinationKeyPath;
expectation.evaluationBlock = testBlock;
return expectation;
}
- (NSString *)description {
if (self.value) {
return [NSString stringWithFormat:@"expected sourceKeyPath '%@' to map to destinationKeyPath '%@' with value: %@", self.sourceKeyPath, self.destinationKeyPath, self.value];
} else if (self.evaluationBlock) {
return [NSString stringWithFormat:@"expected sourceKeyPath '%@' to map to destinationKeyPath '%@' satisfying evaluation block", self.sourceKeyPath, self.destinationKeyPath];
}
return [NSString stringWithFormat:@"expected sourceKeyPath '%@' to map to destinationKeyPath '%@'", self.sourceKeyPath, self.destinationKeyPath];
}
@end

View File

@@ -0,0 +1,133 @@
//
// RKTestFactory.h
// RKGithub
//
// Created by Blake Watters on 2/16/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import <RestKit/RestKit.h>
/**
RKTestFactory provides an interface for initializing RestKit
objects within a unit testing environment.
*/
@interface RKTestFactory : NSObject
///-----------------------------------------------------------------------------
/// @name Configuring the Factory
///-----------------------------------------------------------------------------
/**
The baseURL with which to initialize RKClient and RKObjectManager
instances created via the factory.
*/
@property (nonatomic, strong) RKURL *baseURL;
/**
The class to use when instantiating new client instances.
**Default**: [RKClient class]
*/
@property (nonatomic, strong) Class clientClass;
/**
The class to use when instantiating new object manager instances.
**Default**: [RKObjectManager class]
*/
@property (nonatomic, strong) Class objectManagerClass;
///-----------------------------------------------------------------------------
/// @name Accessing the Shared Factory Instance
///-----------------------------------------------------------------------------
/**
Returns the shared test factory object.
@return The shared test factory.
*/
+ (RKTestFactory *)sharedFactory;
///-----------------------------------------------------------------------------
/// @name Building Instances
///-----------------------------------------------------------------------------
/**
Create and return an RKClient instance.
*/
- (RKClient *)client;
/**
Create and return an RKObjectManager instance.
*/
- (RKObjectManager *)objectManager;
/**
Create and return an RKManagedObjectStore instance.
*/
- (RKManagedObjectStore *)objectStore;
/**
Sets up the RestKit testing environment. Invokes the didSetUp callback for application
specific setup.
*/
- (void)setUp;
/**
Tears down the RestKit testing environment by clearing singleton instances, helping to
ensure test case isolation. Invokes the didTearDown callback for application specific
cleanup.
*/
- (void)tearDown;
///-----------------------------------------------------------------------------
/// @name Customizing the Factory
///-----------------------------------------------------------------------------
/**
Application specific initialization point for the sharedFactory.
Called once per unit testing run when the sharedFactory instance is initialized. RestKit
applications can override via a category.
*/
- (void)didInitialize;
/**
Application specific customization point for the sharedFactory.
Invoked each time the factory is asked to set up the environment. RestKit applications
leveraging the factory may override via a category.
*/
- (void)didSetUp;
/**
Application specific customization point for the sharedFactory.
Invoked each time the factory is tearing down the environment. RestKit applications
leveraging the factory may override via a category.
*/
- (void)didTearDown;
@end
/**
Provides a static interface for common tasks on the RKTestFactory
sharedInstance. All methods are static aliases for instance method
counterparts on [RKTestFactory sharedFactory]
*/
@interface RKTestFactory (ConvenienceAliases)
/**
Ensures the test factory has been initialized
*/
+ (void)setUp;
+ (void)tearDown;
+ (RKURL *)baseURL;
+ (void)setBaseURL:(RKURL *)URL;
+ (NSString *)baseURLString;
+ (void)setBaseURLString:(NSString *)baseURLString;
+ (RKClient *)client;
+ (RKObjectManager *)objectManager;
+ (RKManagedObjectStore *)objectStore;
@end

View File

@@ -0,0 +1,133 @@
//
// RKTestFactory.m
// RKGithub
//
// Created by Blake Watters on 2/16/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import "RKTestFactory.h"
static RKTestFactory *sharedFactory = nil;
@implementation RKTestFactory
@synthesize baseURL;
@synthesize clientClass;
@synthesize objectManagerClass;
+ (RKTestFactory *)sharedFactory {
if (! sharedFactory) {
sharedFactory = [RKTestFactory new];
}
return sharedFactory;
}
- (id)init {
self = [super init];
if (self) {
self.baseURL = [RKURL URLWithString:@"http://localhost:4567"];
self.clientClass = [RKClient class];
self.objectManagerClass = [RKObjectManager class];
[self didInitialize];
}
return self;
}
- (RKClient *)client {
RKClient *client = [self.clientClass clientWithBaseURL:self.baseURL];
[RKClient setSharedClient:client];
client.requestQueue.suspended = NO;
return client;
}
- (RKObjectManager *)objectManager {
[RKObjectManager setDefaultMappingQueue:dispatch_queue_create("org.restkit.ObjectMapping", DISPATCH_QUEUE_SERIAL)];
[RKObjectMapping setDefaultDateFormatters:nil];
RKObjectManager *objectManager = [self.objectManagerClass managerWithBaseURL:self.baseURL];
[RKObjectManager setSharedManager:objectManager];
[RKClient setSharedClient:objectManager.client];
// Force reachability determination
[objectManager.client.reachabilityObserver getFlags];
return objectManager;
}
- (RKManagedObjectStore *)objectStore {
RKManagedObjectStore *store = [RKManagedObjectStore objectStoreWithStoreFilename:@"RKTests.sqlite"];
[store deletePersistantStore];
[RKObjectManager sharedManager].objectStore = store;
return store;
}
- (void)setUp {
[self didSetUp];
}
- (void)tearDown {
[RKObjectManager setSharedManager:nil];
[RKClient setSharedClient:nil];
[self didTearDown];
}
#pragma - Customization Hooks
- (void)didInitialize {
// Should be overloaded via a category
}
- (void)didSetUp {
// Should be overloaded via a category
}
- (void)didTearDown {
// Should be overloaded via a category
}
@end
@implementation RKTestFactory (ConvenienceAliases)
+ (void)setUp {
[[RKTestFactory sharedFactory] setUp];
}
+ (RKURL *)baseURL {
return [RKTestFactory sharedFactory].baseURL;
}
+ (void)setBaseURL:(RKURL *)URL {
[RKTestFactory sharedFactory].baseURL = URL;
}
+ (NSString *)baseURLString {
return [[[RKTestFactory sharedFactory] baseURL] absoluteString];
}
+ (void)setBaseURLString:(NSString *)baseURLString {
[[RKTestFactory sharedFactory] setBaseURL:[RKURL URLWithString:baseURLString]];
}
+ (id)client {
return [[RKTestFactory sharedFactory] client];
}
+ (id)objectManager {
return [[RKTestFactory sharedFactory] objectManager];
}
+ (id)objectStore {
return [[RKTestFactory sharedFactory] objectStore];
}
+ (void)tearDown {
[[RKTestFactory sharedFactory] tearDown];
}
@end

View File

@@ -9,3 +9,5 @@
#import "RKTestFixture.h"
#import "RKTestNotificationObserver.h"
#import "RKTestResponseLoader.h"
#import "RKTestFactory.h"
#import "RKMappingTest.h"