Reorganization and cleanups of Unit Tests

* Reorganized tests to accommodate split into Logic & Application.
* Eliminated 'Spec' naming in favor of 'Test' as the suite is entirely based on SenTest.
* Pulled majority of testing support classes up into the library and documented them.
* Introduced RKApplicationTests app for running the RKTableController UI tests
This commit is contained in:
Blake Watters
2012-02-08 18:35:01 -05:00
parent 8dbd2e8ef0
commit 4142ffdb42
322 changed files with 5165 additions and 3364 deletions

View File

@@ -0,0 +1,88 @@
//
// RKTestFixture.h
// RestKit
//
// Created by Blake Watters on 2/1/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
// 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 <Foundation/Foundation.h>
#import <UIKit/UIImage.h>
/**
Provides a static method API for conveniently accessing fixture data
contained within a designated NSBundle. Useful when writing unit tests that
leverage fixture data for testing parsing and object mapping operations.
*/
@interface RKTestFixture : NSObject
/**
Returns the NSBundle object designated as the source location for unit testing fixture data.
@return The NSBundle object designated as the source location for unit testing fixture data
or nil if none has been configured.
*/
+ (NSBundle *)fixtureBundle;
/**
Designates the specified NSBundle object as the source location for unit testing fixture data.
@param bundle The new fixture NSBundle object.
*/
+ (void)setFixtureBundle:(NSBundle *)bundle;
/**
Creates and returns an image object by loading the image data from the fixture identified by the specified file name.
@param fixtureName The name of the fixture file.
@return A new image object for the specified fixture, or nil if the method could not initialize the image from the specified file.
*/
+ (UIImage *)imageWithContentsOfFixture:(NSString *)fixtureName;
/**
Creates and returns a string object by reading data from the fixture identified by the specified file name using UTF-8 encoding.
@param fixtureName The name of the fixture file.
@return A string created by reading data from the specified fixture file using the NSUTF8StringEncoding.
*/
+ (NSString *)stringWithContentsOfFixture:(NSString *)fixtureName;
/**
Creates and returns a data object by reading every byte from the fixture identified by the specified file name.
@param fixtureName The name of the resource file.
@return A data object by reading every byte from the fixture file.
*/
+ (NSData *)dataWithContentsOfFixture:(NSString *)fixtureName;
/**
Returns the MIME Type for the fixture identified by the specified name.
@param fixtureName The name of the fixture file.
@return The MIME Type for the resource file or nil if the file could not be located.
*/
+ (NSString *)MIMETypeForFixture:(NSString *)fixtureName;
/**
Creates and returns an object representation of the data from the fixture identified by the specified file name by reading the
data as a string and parsing it using a parser appropriate for the MIME Type of the file.
@param fixtureName The name of the resource file.
@return A new image object for the specified file, or nil if the method could not initialize the image from the specified file.
@see RKParserRegistry
*/
+ (id)parsedObjectWithContentsOfFixture:(NSString *)fixtureName;
@end

View File

@@ -0,0 +1,58 @@
//
// RKTestFixture.m
// RestKit
//
// Created by Blake Watters on 2/1/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
// 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 "RKTestFixture.h"
#import "NSBundle+RKAdditions.h"
static NSBundle *fixtureBundle = nil;
@implementation RKTestFixture
+ (NSBundle *)fixtureBundle {
return fixtureBundle;
}
+ (void)setFixtureBundle:(NSBundle *)bundle {
[bundle retain];
[fixtureBundle release];
fixtureBundle = bundle;
}
+ (UIImage *)imageWithContentsOfFixture:(NSString *)fixtureName {
return [fixtureBundle imageWithContentsOfResource:fixtureName withExtension:nil];
}
+ (NSString *)stringWithContentsOfFixture:(NSString *)fixtureName {
return [fixtureBundle stringWithContentsOfResource:fixtureName withExtension:nil encoding:NSUTF8StringEncoding];
}
+ (NSData *)dataWithContentsOfFixture:(NSString *)fixtureName {
return [fixtureBundle dataWithContentsOfResource:fixtureName withExtension:nil];
}
+ (NSString *)MIMETypeForFixture:(NSString *)fixtureName {
return [fixtureBundle MIMETypeForResource:fixtureName withExtension:nil];
}
+ (id)parsedObjectWithContentsOfFixture:(NSString *)fixtureName {
return [fixtureBundle parsedObjectWithContentsOfResource:fixtureName withExtension:nil];
}
@end

View File

@@ -0,0 +1,90 @@
//
// RKTestNotificationObserver.h
// RestKit
//
// Created by Jeff Arena on 8/23/11.
// Copyright 2011 RestKit. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
An RKTestNotificationObserver object provides support for awaiting a notification
to be posted as the result of an asynchronous operation by spinning the run loop. This
enables a straight-forward unit testing workflow by blocking execution of the test until
a notification is posted.
*/
@interface RKTestNotificationObserver : NSObject
/**
The name of the notification the receiver is awaiting.
*/
@property (nonatomic, copy) NSString* name;
/**
The object expected to post the notification the receiver is awaiting.
Can be nil.
*/
@property (nonatomic, assign) id object;
/**
The timeout interval, in seconds, to wait for the notification to be posted.
**Default**: 3 seconds
*/
@property (nonatomic, assign) NSTimeInterval timeout;
/**
Creates and initializes a notification obsercer object.
@return The newly created notification observer.
*/
+ (RKTestNotificationObserver *)notificationObserver;
/**
Instantiate a notification observer for the given notification name and object
@param notificationName The name of the NSNotification we want to watch for
@param notificationSender The source object of the NSNotification we want to watch for
@return The newly created notification observer initialized with notificationName and notificationSender.
*/
+ (RKTestNotificationObserver *)notificationObserverForName:(NSString *)notificationName object:(id)notificationSender;
/**
Instantiate a notification observer for the given notification name
@param notificationName The name of the NSNotification we want to watch for
*/
+ (RKTestNotificationObserver *)notificationObserverForName:(NSString *)notificationName;
/**
Wait for a notification matching the name and source object we are observing to be posted.
This method will block by spinning the runloop waiting for an appropriate notification matching
our observed name and object to be posted or the timeout configured is exceeded.
*/
- (void)waitForNotification;
/*** @name Block Helpers */
/**
Configures a notification observer to wait for the a notification with the given name to be posted
by the source object during execution of the block.
@param name The name of the notification we are waiting for
@param notificationSender The object we are waiting to post the notification
@param block A block to invoke to trigger the notification activity
*/
+ (void)waitForNotificationWithName:(NSString *)name object:(id)notificationSender usingBlock:(void(^)())block;
/**
Configures a notification observer to wait for the a notification with the given name to be posted
during execution of the block.
@param name The name of the notification we are waiting for
@param block A block to invoke to trigger the notification activity
*/
+ (void)waitForNotificationWithName:(NSString *)name usingBlock:(void(^)())block;
@end

View File

@@ -0,0 +1,88 @@
//
// RKTestNotificationObserver.m
// RestKit
//
// Created by Jeff Arena on 8/23/11.
// Copyright 2011 RestKit. All rights reserved.
//
#import "RKTestNotificationObserver.h"
@interface RKTestNotificationObserver ()
@property (nonatomic, assign, getter = isAwaitingNotification) BOOL awaitingNotification;
@end
@implementation RKTestNotificationObserver
@synthesize object;
@synthesize name;
@synthesize timeout;
@synthesize awaitingNotification;
+ (void)waitForNotificationWithName:(NSString *)name object:(id)object usingBlock:(void(^)())block {
RKTestNotificationObserver *observer = [RKTestNotificationObserver notificationObserverForName:name object:object];
block();
[observer waitForNotification];
}
+ (void)waitForNotificationWithName:(NSString *)name usingBlock:(void(^)())block {
[self waitForNotificationWithName:name object:nil usingBlock:block];
}
+ (RKTestNotificationObserver *)notificationObserver {
return [[[self alloc] init] autorelease];
}
+ (RKTestNotificationObserver *)notificationObserverForName:(NSString *)notificationName object:(id)object {
RKTestNotificationObserver *notificationObserver = [self notificationObserver];
notificationObserver.object = object;
notificationObserver.name = notificationName;
return notificationObserver;
}
+ (RKTestNotificationObserver *)notificationObserverForName:(NSString *)notificationName {
return [self notificationObserverForName:notificationName object:nil];
}
- (id)init {
self = [super init];
if (self) {
timeout = 5;
awaitingNotification = NO;
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)waitForNotification {
NSAssert(name, @"Notification name cannot be nil");
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(processNotification:)
name:self.name
object:self.object];
awaitingNotification = YES;
NSDate *startDate = [NSDate date];
while (self.isAwaitingNotification) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
if ([[NSDate date] timeIntervalSinceDate:startDate] > self.timeout) {
[NSException raise:nil format:@"*** Operation timed out after %f seconds...", self.timeout];
awaitingNotification = NO;
}
}
}
- (void)processNotification:(NSNotification*)notification {
NSAssert([name isEqualToString:notification.name],
@"Received notification (%@) differs from expected notification (%@)",
notification.name, name);
awaitingNotification = NO;
}
@end

View File

@@ -0,0 +1,108 @@
//
// RKTestResponseLoader.h
// RestKit
//
// Created by Blake Watters on 1/14/10.
// Copyright 2010 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 <Foundation/Foundation.h>
#import "RKObjectLoader.h"
/**
An RKTestResponseLoader object provides testing support for asynchronously loading an RKRequest or
RKObjectLoader object while blocking the execution of the current thread by spinning the run loop.
This enables a straight-forward unit testing workflow for asynchronous network operations.
RKTestResponseLoader instances are designed to act as as the delegate for an RKObjectLoader or RKRequest
object under test. Once assigned as the delegate to a request and the request has been sent,
waitForResponse: is invoked to block execution until the response is loaded.
*/
@interface RKTestResponseLoader : NSObject <RKObjectLoaderDelegate, RKOAuthClientDelegate>
/**
The RKResponse object loaded from the RKRequest or RKObjectLoader the receiver is acting as the delegate for.
**/
@property (nonatomic, retain, readonly) RKResponse *response;
/**
The collection of objects loaded from the RKObjectLoader the receiver is acting as the delegate for.
*/
@property (nonatomic, retain, readonly) NSArray *objects;
/**
A Boolean value that indicates whether a response was loaded successfully.
@return YES if a response was loaded successfully.
*/
@property (nonatomic, readonly, getter = wasSuccessful) BOOL successful;
/**
A Boolean value that indicates whether the RKRequest or RKObjectLoader the receiver is acting as the delegate for was cancelled.
@return YES if the request was cancelled
*/
@property (nonatomic, readonly, getter = wasCancelled) BOOL cancelled;
/**
A Boolean value that indicates if an unexpected response was loaded.
@return YES if the request loaded an unknown response.
@see [RKObjectLoaderDelegate objectLoaderDidLoadUnexpectedResponse:]
*/
@property (nonatomic, readonly, getter = loadedUnexpectedResponse) BOOL unexpectedResponse;
/**
An NSError value that was loaded from the RKRequest or RKObjectLoader the receiver is acting as the delegate for.
@see [RKRequestDelegate request:didFailLoadWithError:]
@see [RKObjectLoaderDelegate objectLoader:didFailWithError:]
*/
@property (nonatomic, copy, readonly) NSError *error;
/**
The timeout interval, in seconds, to wait for a response to load.
The default value is 4 seconds.
@see [RKTestResponseLoader waitForResponse]
*/
@property (nonatomic, assign) NSTimeInterval timeout;
/**
Creates and returns a test response loader object.
@return A new response loader object.
*/
+ (id)responseLoader;
/**
Waits for an asynchronous RKRequest or RKObjectLoader network operation to load a response
by spinning the current run loop to block the current thread of execution.
The wait operation is guarded by a timeout
*/
- (void)waitForResponse;
/**
Returns the localized description error message for the error.
TODO: Why not just move this to NSError+RKAdditions?
@return The localized description of the error or nil.
*/
- (NSString *)errorMessage;
@end

View File

@@ -0,0 +1,160 @@
//
// RKTestResponseLoader.m
// RestKit
//
// Created by Blake Watters on 1/14/10.
// Copyright 2010 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 "RKTestResponseLoader.h"
#import "RKLog.h"
// Set Logging Component
#undef RKLogComponent
#define RKLogComponent lcl_cRestKitTesting
NSString * const RKTestResponseLoaderTimeoutException = @"RKTestResponseLoaderTimeoutException";
@interface RKTestResponseLoader ()
@property (nonatomic, assign, getter = isAwaitingResponse) BOOL awaitingResponse;
@property (nonatomic, retain, readwrite) RKResponse *response;
@property (nonatomic, copy, readwrite) NSError *error;
@property (nonatomic, retain, readwrite) NSArray *objects;
@end
@implementation RKTestResponseLoader
@synthesize response;
@synthesize objects;
@synthesize error;
@synthesize successful;
@synthesize timeout;
@synthesize cancelled;
@synthesize unexpectedResponse;
@synthesize awaitingResponse;
+ (RKTestResponseLoader *)responseLoader {
return [[[self alloc] init] autorelease];
}
- (id)init {
self = [super init];
if (self) {
timeout = 4;
awaitingResponse = NO;
}
return self;
}
- (void)dealloc {
[response release];
response = nil;
[error release];
error = nil;
[objects release];
objects = nil;
[super dealloc];
}
- (void)waitForResponse {
awaitingResponse = YES;
NSDate *startDate = [NSDate date];
RKLogTrace(@"%@ Awaiting response loaded from for %f seconds...", self, self.timeout);
while (awaitingResponse) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
if ([[NSDate date] timeIntervalSinceDate:startDate] > self.timeout) {
[NSException raise:RKTestResponseLoaderTimeoutException format:@"*** Operation timed out after %f seconds...", self.timeout];
awaitingResponse = NO;
}
}
}
- (void)loadError:(NSError *)theError {
awaitingResponse = NO;
successful = NO;
self.error = theError;
}
- (NSString *)errorMessage {
if (self.error) {
return [[self.error userInfo] valueForKey:NSLocalizedDescriptionKey];
}
return nil;
}
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)aResponse {
self.response = aResponse;
// If request is an Object Loader, then objectLoader:didLoadObjects:
// will be sent after didLoadResponse:
if (NO == [request isKindOfClass:[RKObjectLoader class]]) {
awaitingResponse = NO;
successful = YES;
}
}
- (void)request:(RKRequest *)request didFailLoadWithError:(NSError *)anError {
// If request is an Object Loader, then objectLoader:didFailWithError:
// will be sent after didFailLoadWithError:
if (NO == [request isKindOfClass:[RKObjectLoader class]]) {
[self loadError:anError];
}
}
- (void)requestDidCancelLoad:(RKRequest *)request {
awaitingResponse = NO;
successful = NO;
cancelled = YES;
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)theObjects {
RKLogTrace(@"%@ Loaded response for %@ with body: %@", self, objectLoader, [objectLoader.response bodyAsString]);
RKLogDebug(@"%@ Loaded objects for %@: %@", self, objectLoader, objects);
self.objects = theObjects;
awaitingResponse = NO;
successful = YES;
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)theError {
[self loadError:theError];
}
- (void)objectLoaderDidLoadUnexpectedResponse:(RKObjectLoader *)objectLoader {
RKLogDebug(@"%@ Loaded unexpected response for: %@", self, objectLoader);
successful = NO;
awaitingResponse = NO;
unexpectedResponse = YES;
}
#pragma mark - OAuth delegates
- (void)OAuthClient:(RKOAuthClient *)client didAcquireAccessToken:(NSString *)token {
awaitingResponse = NO;
successful = YES;
}
- (void)OAuthClient:(RKOAuthClient *)client didFailWithInvalidGrantError:(NSError *)error {
awaitingResponse = NO;
successful = NO;
}
@end

11
Code/Testing/Testing.h Normal file
View File

@@ -0,0 +1,11 @@
//
// Testing.h
// RestKit
//
// Created by Blake Watters on 2/1/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#import "RKTestFixture.h"
#import "RKTestNotificationObserver.h"
#import "RKTestResponseLoader.h"