Skip to content

Commit

Permalink
added asynchronous testing support.
Browse files Browse the repository at this point in the history
  • Loading branch information
petejkim committed Jul 12, 2011
1 parent 106be93 commit a73e19f
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 33 deletions.
12 changes: 12 additions & 0 deletions Expecta.xcodeproj/project.pbxproj
Expand Up @@ -67,6 +67,8 @@
E9685DC013B5E5DA00ADF2D7 /* ExpectaSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = E9685DBE13B5E5DA00ADF2D7 /* ExpectaSupport.m */; };
E969C52513B60234006BB8B1 /* EXPFailTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E969C52213B5FD02006BB8B1 /* EXPFailTest.m */; };
E969C52613B60234006BB8B1 /* EXPFailTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E969C52213B5FD02006BB8B1 /* EXPFailTest.m */; };
E9AA7A5D13CC874F005884E8 /* Expecta.m in Sources */ = {isa = PBXBuildFile; fileRef = E9AA7A5C13CC874F005884E8 /* Expecta.m */; };
E9AA7A5E13CC874F005884E8 /* Expecta.m in Sources */ = {isa = PBXBuildFile; fileRef = E9AA7A5C13CC874F005884E8 /* Expecta.m */; };
E9ACDF2113B2DD520010F4D7 /* libExpecta.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E9ACDF0C13B2DD520010F4D7 /* libExpecta.a */; };
E9ACDF5813B2DDC60010F4D7 /* EXPExpect.h in Headers */ = {isa = PBXBuildFile; fileRef = E9ACDF3D13B2DDC60010F4D7 /* EXPExpect.h */; settings = {ATTRIBUTES = (Public, ); }; };
E9ACDF5913B2DDC60010F4D7 /* EXPExpect.m in Sources */ = {isa = PBXBuildFile; fileRef = E9ACDF3E13B2DDC60010F4D7 /* EXPExpect.m */; };
Expand Down Expand Up @@ -105,6 +107,8 @@
E9ACDF9E13B2E0BB0010F4D7 /* EXPExpect+Test.m in Sources */ = {isa = PBXBuildFile; fileRef = E9ACDF9813B2E0BB0010F4D7 /* EXPExpect+Test.m */; };
E9ACDF9F13B2E0BB0010F4D7 /* FakeTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = E9ACDF9A13B2E0BB0010F4D7 /* FakeTestCase.m */; };
E9ACDFA013B2E0BB0010F4D7 /* Fixtures.m in Sources */ = {isa = PBXBuildFile; fileRef = E9ACDF9C13B2E0BB0010F4D7 /* Fixtures.m */; };
E9AFC43A13C482D3006B4F2A /* AsynchronousTestingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E9AFC43913C482D3006B4F2A /* AsynchronousTestingTest.m */; };
E9AFC43B13C482D3006B4F2A /* AsynchronousTestingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E9AFC43913C482D3006B4F2A /* AsynchronousTestingTest.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -138,6 +142,7 @@
E9685DBE13B5E5DA00ADF2D7 /* ExpectaSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExpectaSupport.m; sourceTree = "<group>"; };
E969C52213B5FD02006BB8B1 /* EXPFailTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXPFailTest.m; sourceTree = "<group>"; };
E9A6BAAE13B7183100950250 /* RDD.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = RDD.md; sourceTree = "<group>"; };
E9AA7A5C13CC874F005884E8 /* Expecta.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Expecta.m; sourceTree = "<group>"; };
E9ACDF0C13B2DD520010F4D7 /* libExpecta.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libExpecta.a; sourceTree = BUILT_PRODUCTS_DIR; };
E9ACDF1713B2DD520010F4D7 /* Expecta-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Expecta-Prefix.pch"; sourceTree = "<group>"; };
E9ACDF1D13B2DD520010F4D7 /* ExpectaTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExpectaTests.octest; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -186,6 +191,7 @@
E9ACDF9D13B2E0BB0010F4D7 /* TestHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestHelper.h; sourceTree = "<group>"; };
E9ACDFA213B2E0CC0010F4D7 /* Test-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Test-Info.plist"; sourceTree = "<group>"; };
E9ACDFA313B2E0CC0010F4D7 /* Test-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Test-Prefix.pch"; sourceTree = "<group>"; };
E9AFC43913C482D3006B4F2A /* AsynchronousTestingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsynchronousTestingTest.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -264,6 +270,7 @@
E9ACDF4313B2DDC60010F4D7 /* matchers */,
E9ACDF1713B2DD520010F4D7 /* Expecta-Prefix.pch */,
E9ACDF3F13B2DDC60010F4D7 /* Expecta.h */,
E9AA7A5C13CC874F005884E8 /* Expecta.m */,
E9685DBB13B5E39300ADF2D7 /* ExpectaSupport.h */,
E9685DBE13B5E5DA00ADF2D7 /* ExpectaSupport.m */,
E9ACDF3D13B2DDC60010F4D7 /* EXPExpect.h */,
Expand Down Expand Up @@ -314,6 +321,7 @@
E9ACDF7313B2DEB70010F4D7 /* ExpectationTest.m */,
E9ACDF7C13B2DEB70010F4D7 /* NSValue+ExpectaTest.m */,
E969C52213B5FD02006BB8B1 /* EXPFailTest.m */,
E9AFC43913C482D3006B4F2A /* AsynchronousTestingTest.m */,
);
path = test;
sourceTree = "<group>";
Expand Down Expand Up @@ -576,6 +584,7 @@
E94296FB13B430DF0038708B /* EXPMatchers+toContain.m in Sources */,
E942971913B45C8F0038708B /* EXPMatchers+toBeIdenticalTo.m in Sources */,
E9685DC013B5E5DA00ADF2D7 /* ExpectaSupport.m in Sources */,
E9AA7A5E13CC874F005884E8 /* Expecta.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -598,6 +607,7 @@
E94296F513B42E160038708B /* EXPMatchers+toContainTest.m in Sources */,
E942971313B45ADD0038708B /* EXPMatchers+toBeIdenticalToTest.m in Sources */,
E969C52613B60234006BB8B1 /* EXPFailTest.m in Sources */,
E9AFC43B13C482D3006B4F2A /* AsynchronousTestingTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -619,6 +629,7 @@
E94296FA13B430DF0038708B /* EXPMatchers+toContain.m in Sources */,
E942971813B45C8F0038708B /* EXPMatchers+toBeIdenticalTo.m in Sources */,
E9685DBF13B5E5DA00ADF2D7 /* ExpectaSupport.m in Sources */,
E9AA7A5D13CC874F005884E8 /* Expecta.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -641,6 +652,7 @@
E94296F413B42E160038708B /* EXPMatchers+toContainTest.m in Sources */,
E942971213B45ADD0038708B /* EXPMatchers+toBeIdenticalToTest.m in Sources */,
E969C52513B60234006BB8B1 /* EXPFailTest.m in Sources */,
E9AFC43A13C482D3006B4F2A /* AsynchronousTestingTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
12 changes: 11 additions & 1 deletion README.md
Expand Up @@ -71,10 +71,20 @@ Expecta works best with [Cedar BDD Framework](http://pivotal.github.com/cedar/).
**More matchers are coming soon!**

Every matcher's criteria can be inverted by prepending `.Not`: (It is `.Not` with a capital `N` because `not` is a keyword in C++.)
## INVERTING MATCHERS

Every matcher's criteria can be inverted by prepending `.Not`: (It is with a capital `N` because `not` is a keyword in C++.)

>`expect(x).Not.toEqual(y);` compares objects or primitives x and y and passes if they are *not* equivalent.
## ASYNCHRONOUS TESTING

Every matcher can be made to perform asynchronous testing by prepending `.isGoing` or `.isNotGoing`:

>`expect(x).isGoing.toBeNil();` passes if x eventually becomes nil within the timeout.
Default timeout is 1.0 second. This setting can be changed by calling `[Expecta setAsynchronousTestTimeout:x]`, where x is the desired timeout.

## WRITING NEW MATCHERS

Writing a new matcher is easy with special macros provided by Expecta. Take a look at how `.toBeKindOf()` matcher is defined:
Expand Down
16 changes: 11 additions & 5 deletions src/EXPExpect.h
Expand Up @@ -4,15 +4,17 @@

#import <Foundation/Foundation.h>

typedef id (^EXPIdBlock)();
typedef BOOL (^EXPBoolBlock)();
typedef NSString *(^EXPStringBlock)();

@interface EXPExpect : NSObject {
id _actual;
EXPIdBlock _actualBlock;
id _testCase;
int _lineNumber;
char *_fileName;
BOOL _negative;
BOOL _asynchronous;

EXPBoolBlock _prerequisiteBlock;
EXPBoolBlock _matchBlock;
Expand All @@ -25,17 +27,21 @@ typedef NSString *(^EXPStringBlock)();
void (^failureMessageForNotTo)(EXPStringBlock block);
}

@property(nonatomic, assign) id actual;
@property(nonatomic, copy) EXPIdBlock actualBlock;
@property(nonatomic, readonly) id actual;
@property(nonatomic, assign) id testCase;
@property(nonatomic) int lineNumber;
@property(nonatomic) char *fileName;
@property(nonatomic) BOOL negative;
@property(nonatomic) BOOL asynchronous;

@property(nonatomic, readonly) EXPExpect *Not;
@property(nonatomic, readonly) EXPExpect *isGoing;
@property(nonatomic, readonly) EXPExpect *isNotGoing;

- (id)initWithActual:(id)actual testCase:(id)testCase lineNumber:(int)lineNumber fileName:(char *)fileName;
+ (EXPExpect *)expectWithActual:(id)actual testCase:(id)testCase lineNumber:(int)lineNumber fileName:(char *)fileName;
- (id)initWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(char *)fileName;
+ (EXPExpect *)expectWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(char *)fileName;

- (void)applyMatcher;
- (void)applyMatcher:(NSObject **)actual;

@end
62 changes: 52 additions & 10 deletions src/EXPExpect.m
Expand Up @@ -4,7 +4,8 @@

#import "EXPExpect.h"
#import "NSObject+Expecta.h"
#import "ExpectaSupport.h"
#import "Expecta.h"
#import "EXPUnsupportedObject.h"

@interface EXPExpect (PrivateMethods)

Expand All @@ -15,20 +16,26 @@ - (void)initializeMatcherFunctions;
@implementation EXPExpect

@dynamic
Not;
actual,
Not,
isGoing,
isNotGoing;

@synthesize
actual=_actual,
actualBlock=_actualBlock,
testCase=_testCase,
negative=_negative,
asynchronous=_asynchronous,
lineNumber=_lineNumber,
fileName=_fileName;

- (id)initWithActual:(id)actual testCase:(id)testCase lineNumber:(int)lineNumber fileName:(char *)fileName {
- (id)initWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(char *)fileName {
self = [super init];
if(self) {
self.actual = actual;
self.actualBlock = actualBlock;
self.testCase = testCase;
self.negative = NO;
self.asynchronous = NO;
self.lineNumber = lineNumber;
self.fileName = fileName;
_prerequisiteBlock = nil;
Expand All @@ -40,15 +47,17 @@ - (id)initWithActual:(id)actual testCase:(id)testCase lineNumber:(int)lineNumber
return self;
}

+ (EXPExpect *)expectWithActual:(id)actual testCase:(id)testCase lineNumber:(int)lineNumber fileName:(char *)fileName {
return [[[EXPExpect alloc] initWithActual:actual testCase:(id)testCase lineNumber:lineNumber fileName:fileName] autorelease];
+ (EXPExpect *)expectWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(char *)fileName {
return [[[EXPExpect alloc] initWithActualBlock:actualBlock testCase:(id)testCase lineNumber:lineNumber fileName:fileName] autorelease];
}

- (void)dealloc {
[_actualBlock release];
[_prerequisiteBlock release];
[_matchBlock release];
[_failureMessageForToBlock release];
[_failureMessageForNotToBlock release];
[prerequisite release];
[match release];
[failureMessageForTo release];
[failureMessageForNotTo release];
Expand All @@ -62,8 +71,24 @@ - (EXPExpect *)Not {
return self;
}

- (EXPExpect *)isGoing {
self.asynchronous = YES;
return self;
}

- (EXPExpect *)isNotGoing {
return self.isGoing.Not;
}

#pragma mark -

- (id)actual {
if(self.actualBlock) {
return self.actualBlock();
}
return nil;
}

- (void)initializeMatcherFunctions {
prerequisite = [^(EXPBoolBlock block) {
[_prerequisiteBlock release];
Expand All @@ -83,13 +108,30 @@ - (void)initializeMatcherFunctions {
} copy];
}

- (void)applyMatcher {
if(_matchBlock) {
- (void)applyMatcher:(NSObject **)actual {
if([*actual isKindOfClass:[EXPUnsupportedObject class]]) {
EXPFail(self.testCase, self.lineNumber, self.fileName,
[NSString stringWithFormat:@"expecting a %@ is not supported", ((EXPUnsupportedObject *)*actual).type]);
} else if(_matchBlock) {
BOOL failed;
if(_prerequisiteBlock && !_prerequisiteBlock()) {
failed = YES;
} else {
BOOL matchResult = _matchBlock();
BOOL matchResult;
if(self.asynchronous) {
NSTimeInterval timeOut = [Expecta asynchronousTestTimeout];
NSDate *expiryDate = [NSDate dateWithTimeIntervalSinceNow:timeOut];
while(1) {
matchResult = _matchBlock();
if(matchResult || ([(NSDate *)[NSDate date] compare:expiryDate] == NSOrderedDescending)) {
break;
}
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
*actual = self.actual;
}
} else {
matchResult = _matchBlock();
}
failed = self.negative ? matchResult : !matchResult;
}
if(failed) {
Expand Down
9 changes: 8 additions & 1 deletion src/Expecta.h
Expand Up @@ -7,7 +7,7 @@

#define EXPObjectify(value) _EXPObjectify(@encode(__typeof__((value))), (value))

#define EXP_expect(actual) _EXP_expect(self, __LINE__, __FILE__, EXPObjectify((actual)))
#define EXP_expect(actual) _EXP_expect(self, __LINE__, __FILE__, ^id{ return EXPObjectify((actual)); })

#define EXPMatcherInterface(matcherName, matcherArguments) _EXPMatcherInterface(matcherName, matcherArguments)
#define EXPMatcherImplementationBegin(matcherName, matcherArguments) _EXPMatcherImplementationBegin(matcherName, matcherArguments)
Expand All @@ -18,3 +18,10 @@
#ifdef EXP_SHORTHAND
# define expect(actual) EXP_expect((actual))
#endif

@interface Expecta : NSObject

+ (NSTimeInterval)asynchronousTestTimeout;
+ (void)setAsynchronousTestTimeout:(NSTimeInterval)timeout;

@end
15 changes: 15 additions & 0 deletions src/Expecta.m
@@ -0,0 +1,15 @@
#import "Expecta.h"

@implementation Expecta

static NSTimeInterval _asynchronousTestTimeout = 1.0;

+ (NSTimeInterval)asynchronousTestTimeout {
return _asynchronousTestTimeout;
}

+ (void)setAsynchronousTestTimeout:(NSTimeInterval)timeout {
_asynchronousTestTimeout = timeout;
}

@end
6 changes: 3 additions & 3 deletions src/ExpectaSupport.h
Expand Up @@ -5,7 +5,7 @@
#import "EXPExpect.h"

id _EXPObjectify(char *type, ...);
EXPExpect *_EXP_expect(id testCase, int lineNumber, char *fileName, id actual);
EXPExpect *_EXP_expect(id testCase, int lineNumber, char *fileName, EXPIdBlock actualBlock);

void EXPFail(id testCase, int lineNumber, char *fileName, NSString *message);
NSString *EXPDescribeObject(id obj);
Expand All @@ -25,13 +25,13 @@ EXPFixCategoriesBug(EXPMatcher##matcherName##Matcher); \
@implementation EXPExpect (matcherName##Matcher) \
@dynamic matcherName;\
- (void(^) matcherArguments) matcherName { \
id actual = self.actual; \
__block id actual = self.actual; \
void (^matcherBlock) matcherArguments = ^ matcherArguments { \
{

#define _EXPMatcherImplementationEnd \
} \
[self applyMatcher]; \
[self applyMatcher:&actual]; \
}; \
return [[matcherBlock copy] autorelease]; \
} \
Expand Down
13 changes: 7 additions & 6 deletions src/ExpectaSupport.m
Expand Up @@ -7,6 +7,8 @@
#import "NSObject+Expecta.h"
#import "EXPUnsupportedObject.h"

typedef void (^EXPBasicBlock)();

id _EXPObjectify(char *type, ...) {
va_list v;
va_start(v, type);
Expand Down Expand Up @@ -52,6 +54,9 @@ id _EXPObjectify(char *type, ...) {
obj = actual;
} else if(strcmp(type, @encode(__typeof__(nil))) == 0) {
obj = nil;
} else if(strcmp(type, @encode(EXPBasicBlock)) == 0) {
EXPUnsupportedObject *actual = [[[EXPUnsupportedObject alloc] initWithType:@"block"] autorelease];
obj = actual;
} else if(type[0] == '{') {
EXPUnsupportedObject *actual = [[[EXPUnsupportedObject alloc] initWithType:@"struct"] autorelease];
obj = actual;
Expand All @@ -69,12 +74,8 @@ id _EXPObjectify(char *type, ...) {
return obj;
}

EXPExpect *_EXP_expect(id testCase, int lineNumber, char *fileName, id actual) {
if([actual isKindOfClass:[EXPUnsupportedObject class]]) {
EXPFail(testCase, lineNumber, fileName, [NSString stringWithFormat:@"expecting a %@ is not supported", ((EXPUnsupportedObject *)actual).type]);
return nil;
}
return [EXPExpect expectWithActual:actual testCase:testCase lineNumber:lineNumber fileName:fileName];
EXPExpect *_EXP_expect(id testCase, int lineNumber, char *fileName, EXPIdBlock actualBlock) {
return [EXPExpect expectWithActualBlock:actualBlock testCase:testCase lineNumber:lineNumber fileName:fileName];
}

void EXPFail(id testCase, int lineNumber, char *fileName, NSString *message) {
Expand Down

0 comments on commit a73e19f

Please sign in to comment.