diff --git a/Source/OCMockito.xcodeproj/project.pbxproj b/Source/OCMockito.xcodeproj/project.pbxproj index f1daecfc..87688a07 100644 --- a/Source/OCMockito.xcodeproj/project.pbxproj +++ b/Source/OCMockito.xcodeproj/project.pbxproj @@ -124,6 +124,8 @@ 638F68DA150FC21A0081DEE6 /* MKTClassObjectMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 638F68D6150FC21A0081DEE6 /* MKTClassObjectMock.m */; }; 638F68DD150FC3210081DEE6 /* MKTClassObjectMockTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 638F68DC150FC3210081DEE6 /* MKTClassObjectMockTest.m */; }; 638F68DE150FC3210081DEE6 /* MKTClassObjectMockTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 638F68DC150FC3210081DEE6 /* MKTClassObjectMockTest.m */; }; + 6525714317D14C2300577580 /* MKTExactTimesTest.m in Resources */ = {isa = PBXBuildFile; fileRef = 6525714217D14C2300577580 /* MKTExactTimesTest.m */; }; + 6525714417D1510100577580 /* MKTExactTimesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6525714217D14C2300577580 /* MKTExactTimesTest.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -223,6 +225,7 @@ 638F68D5150FC21A0081DEE6 /* MKTClassObjectMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MKTClassObjectMock.h; sourceTree = ""; }; 638F68D6150FC21A0081DEE6 /* MKTClassObjectMock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MKTClassObjectMock.m; sourceTree = ""; }; 638F68DC150FC3210081DEE6 /* MKTClassObjectMockTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MKTClassObjectMockTest.m; sourceTree = ""; }; + 6525714217D14C2300577580 /* MKTExactTimesTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MKTExactTimesTest.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -351,22 +354,23 @@ 08BDD5C31350C59C00E5A443 /* Tests */ = { isa = PBXGroup; children = ( + 085CE8F1172A0D43004D070D /* BlockMatchingTest.m */, 361533CB153F283800BB982B /* MKTAtLeastTimesTest.m */, + 6525714217D14C2300577580 /* MKTExactTimesTest.m */, 638F68DC150FC3210081DEE6 /* MKTClassObjectMockTest.m */, - 089A8C2213552DE7003E7D27 /* MKTObjectMockTest.m */, - 08E9DC271516739A00BD7FB4 /* MKTObjectAndProtocolMockTest.m */, - 085A8BC6150718E30018EC66 /* MKTProtocolMockTest.m */, 08F189FC135A6D1300F76379 /* MKTInvocationMatcherTest.m */, 08F189E21359661E00F76379 /* MKTMockingProgressTest.m */, - 085CE8F1172A0D43004D070D /* BlockMatchingTest.m */, + 08E9DC271516739A00BD7FB4 /* MKTObjectAndProtocolMockTest.m */, + 089A8C2213552DE7003E7D27 /* MKTObjectMockTest.m */, + 085A8BC6150718E30018EC66 /* MKTProtocolMockTest.m */, 08FD4B1E1509A6C40004E1FA /* MockTestCase.h */, 08FD4B1F1509A6C40004E1FA /* MockTestCase.m */, 089A8C3113555D0C003E7D27 /* StubObjectTest.m */, 08FD4B251509B8740004E1FA /* StubProtocolTest.m */, 08E9DC1B15163D4600BD7FB4 /* VerifyClassObjectTest.m */, - 08FD4B221509A93B0004E1FA /* VerifyProtocolTest.m */, - 085D2FB11351080400EBBE91 /* VerifyObjectTest.m */, 08E9DC2D1516777100BD7FB4 /* VerifyObjectAndProtocolTest.m */, + 085D2FB11351080400EBBE91 /* VerifyObjectTest.m */, + 08FD4B221509A93B0004E1FA /* VerifyProtocolTest.m */, ); path = Tests; sourceTree = ""; @@ -551,6 +555,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6525714317D14C2300577580 /* MKTExactTimesTest.m in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -708,6 +713,7 @@ 08E9DC2E1516777200BD7FB4 /* VerifyObjectAndProtocolTest.m in Sources */, 361533CC153F283800BB982B /* MKTAtLeastTimesTest.m in Sources */, 085CE8F5172A1BCA004D070D /* BlockMatchingTest.m in Sources */, + 6525714417D1510100577580 /* MKTExactTimesTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/OCMockito/MKTAtLeastTimes.h b/Source/OCMockito/MKTAtLeastTimes.h index 7c07c46a..5aaf0fbd 100644 --- a/Source/OCMockito/MKTAtLeastTimes.h +++ b/Source/OCMockito/MKTAtLeastTimes.h @@ -12,7 +12,7 @@ @interface MKTAtLeastTimes : NSObject -+ (id)timesWithMinimumCount:(NSUInteger)minimumExpectedNumberOfInvocations; -- (id)initWithMinimumCount:(NSUInteger)minimumExpectedNumberOfInvocations; ++ (id)timesWithMinimumCount:(NSUInteger)minimumExpectedNumberOfInvocations eventually:(BOOL)eventually; +- (id)initWithMinimumCount:(NSUInteger)minimumExpectedNumberOfInvocations eventually:(BOOL)eventually; @end diff --git a/Source/OCMockito/MKTAtLeastTimes.m b/Source/OCMockito/MKTAtLeastTimes.m index a600972e..f341bc43 100644 --- a/Source/OCMockito/MKTAtLeastTimes.m +++ b/Source/OCMockito/MKTAtLeastTimes.m @@ -8,6 +8,7 @@ #import "MKTAtLeastTimes.h" +#import "OCMockito.h" #import "MKTInvocationContainer.h" #import "MKTInvocationMatcher.h" #import "MKTVerificationData.h" @@ -16,21 +17,36 @@ @implementation MKTAtLeastTimes { NSUInteger _minimumExpectedCount; + BOOL _eventually; } -+ (id)timesWithMinimumCount:(NSUInteger)minimumExpectedNumberOfInvocations ++ (id)timesWithMinimumCount:(NSUInteger)minimumExpectedNumberOfInvocations eventually:(BOOL)eventually { - return [[self alloc] initWithMinimumCount:minimumExpectedNumberOfInvocations]; + return [[self alloc] initWithMinimumCount:minimumExpectedNumberOfInvocations eventually:eventually]; } -- (id)initWithMinimumCount:(NSUInteger)minimumExpectedNumberOfInvocations +- (id)initWithMinimumCount:(NSUInteger)minimumExpectedNumberOfInvocations eventually:(BOOL)eventually { self = [super init]; if (self) + { _minimumExpectedCount = minimumExpectedNumberOfInvocations; + _eventually = eventually; + } return self; } +- (NSUInteger)matchingCountWithData:(MKTVerificationData *)data +{ + NSUInteger matchingCount = 0; + for (NSInvocation *invocation in [[data invocations] registeredInvocations]) + { + if ([[data wanted] matches:invocation]) + ++matchingCount; + } + + return matchingCount; +} #pragma mark MKTVerificationMode @@ -38,14 +54,24 @@ - (void)verifyData:(MKTVerificationData *)data { if (_minimumExpectedCount == 0) return; // this always succeeds - - NSUInteger matchingCount = 0; - for (NSInvocation *invocation in [[data invocations] registeredInvocations]) + + NSUInteger matchingCount; + if (_eventually) { - if ([[data wanted] matches:invocation]) - ++matchingCount; + NSDate *expiryDate = [NSDate dateWithTimeIntervalSinceNow:MKT_eventuallyDefaultTimeout()]; + while (1) + { + matchingCount = [self matchingCountWithData:data]; + if (matchingCount >= _minimumExpectedCount || [(NSDate *)[NSDate date] compare:expiryDate] == NSOrderedDescending) + break; + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; + } + } + else + { + matchingCount = [self matchingCountWithData:data]; } - + if (matchingCount < _minimumExpectedCount) { NSString *plural = (_minimumExpectedCount == 1) ? @"" : @"s"; diff --git a/Source/OCMockito/MKTExactTimes.h b/Source/OCMockito/MKTExactTimes.h index 1fd62390..7520153c 100644 --- a/Source/OCMockito/MKTExactTimes.h +++ b/Source/OCMockito/MKTExactTimes.h @@ -12,7 +12,7 @@ @interface MKTExactTimes : NSObject -+ (id)timesWithCount:(NSUInteger)expectedNumberOfInvocations; -- (id)initWithCount:(NSUInteger)expectedNumberOfInvocations; ++ (id)timesWithCount:(NSUInteger)expectedNumberOfInvocations eventually:(BOOL)eventually; +- (id)initWithCount:(NSUInteger)expectedNumberOfInvocations eventually:(BOOL)eventually; @end diff --git a/Source/OCMockito/MKTExactTimes.m b/Source/OCMockito/MKTExactTimes.m index edb3f5e8..1ba16432 100644 --- a/Source/OCMockito/MKTExactTimes.m +++ b/Source/OCMockito/MKTExactTimes.m @@ -8,6 +8,7 @@ #import "MKTExactTimes.h" +#import "OCMockito.h" #import "MKTInvocationContainer.h" #import "MKTInvocationMatcher.h" #import "MKTTestLocation.h" @@ -25,26 +26,27 @@ - (void)failWithException:(NSException *)exception; @implementation MKTExactTimes { - NSUInteger expectedCount; + NSUInteger _expectedCount; + BOOL _eventually; } -+ (id)timesWithCount:(NSUInteger)expectedNumberOfInvocations ++ (id)timesWithCount:(NSUInteger)expectedNumberOfInvocations eventually:(BOOL)eventually { - return [[self alloc] initWithCount:expectedNumberOfInvocations]; + return [[self alloc] initWithCount:expectedNumberOfInvocations eventually:eventually]; } -- (id)initWithCount:(NSUInteger)expectedNumberOfInvocations +- (id)initWithCount:(NSUInteger)expectedNumberOfInvocations eventually:(BOOL)eventually { self = [super init]; if (self) - expectedCount = expectedNumberOfInvocations; + { + _expectedCount = expectedNumberOfInvocations; + _eventually = eventually; + } return self; } - -#pragma mark MKTVerificationMode - -- (void)verifyData:(MKTVerificationData *)data +- (NSUInteger)matchingCountWithData:(MKTVerificationData *)data { NSUInteger matchingCount = 0; for (NSInvocation *invocation in [[data invocations] registeredInvocations]) @@ -52,12 +54,36 @@ - (void)verifyData:(MKTVerificationData *)data if ([[data wanted] matches:invocation]) ++matchingCount; } - - if (matchingCount != expectedCount) + + return matchingCount; +} + +#pragma mark MKTVerificationMode + +- (void)verifyData:(MKTVerificationData *)data +{ + NSUInteger matchingCount; + if (_eventually) + { + NSDate *expiryDate = [NSDate dateWithTimeIntervalSinceNow:MKT_eventuallyDefaultTimeout()]; + while (1) + { + matchingCount = [self matchingCountWithData:data]; + if (matchingCount > _expectedCount || [(NSDate *)[NSDate date] compare:expiryDate] == NSOrderedDescending) + break; + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; + } + } + else + { + matchingCount = [self matchingCountWithData:data]; + } + + if (matchingCount != _expectedCount) { - NSString *plural = (expectedCount == 1) ? @"" : @"s"; + NSString *plural = (_expectedCount == 1) ? @"" : @"s"; NSString *description = [NSString stringWithFormat:@"Expected %u matching invocation%@, but received %u", - (unsigned)expectedCount, plural, (unsigned)matchingCount]; + (unsigned)_expectedCount, plural, (unsigned)matchingCount]; MKTFailTestLocation([data testLocation], description); } } diff --git a/Source/OCMockito/MKTVerificationMode.h b/Source/OCMockito/MKTVerificationMode.h index f1260dbd..f58dcf55 100644 --- a/Source/OCMockito/MKTVerificationMode.h +++ b/Source/OCMockito/MKTVerificationMode.h @@ -9,6 +9,7 @@ #import @class MKTVerificationData; +@class MKTVerificationModeResult; @protocol MKTVerificationMode diff --git a/Source/OCMockito/OCMockito.h b/Source/OCMockito/OCMockito.h index 3c77271f..dc575878 100644 --- a/Source/OCMockito/OCMockito.h +++ b/Source/OCMockito/OCMockito.h @@ -144,6 +144,7 @@ OBJC_EXPORT id MKTVerifyCountWithLocation(id mock, id mode, id testCase, const c OBJC_EXPORT id MKTTimes(NSUInteger wantedNumberOfInvocations); +OBJC_EXPORT id MKTEventuallyTimes(NSUInteger wantedNumberOfInvocations); /** Verifies exact number of invocations. @@ -160,8 +161,24 @@ OBJC_EXPORT id MKTTimes(NSUInteger wantedNumberOfInvocations); #define times(wantedNumberOfInvocations) MKTTimes(wantedNumberOfInvocations) #endif +/** + Verifies exact number of invocations now, or at some time in the near future. + + Example: +@code +[verifyCount(mockObject, eventuallyTimes(2)) someMethod:@"some arg"]; +@endcode + + (In the event of a name clash, don't \#define @c MOCKITO_SHORTHAND and use the synonym + @c MKTEventuallyTimes instead.) + */ +#ifdef MOCKITO_SHORTHAND + #define eventuallyTimes(wantedNumberOfInvocations) MKTEventuallyTimes(wantedNumberOfInvocations) +#endif + OBJC_EXPORT id MKTNever(void); +OBJC_EXPORT id MKTEventuallyNever(void); /** Verifies that interaction did not happen. @@ -178,8 +195,24 @@ OBJC_EXPORT id MKTNever(void); #define never() MKTNever() #endif +/** + Verifies that interaction did not happen now, or at some time in the near future. + + Example: + @code + [verifyCount(mockObject, eventuallyNever()) someMethod:@"some arg"]; + @endcode + + (In the event of a name clash, don't \#define @c MOCKITO_SHORTHAND and use the synonym + @c MKTEventuallyNever instead.) + */ +#ifdef MOCKITO_SHORTHAND + #define eventuallyNever() MKTEventuallyNever() +#endif + OBJC_EXPORT id MKTAtLeast(NSUInteger minimumWantedNumberOfInvocations); +OBJC_EXPORT id MKTEventuallyAtLeast(NSUInteger minimumWantedNumberOfInvocations); /** Verifies minimum number of invocations. @@ -199,8 +232,27 @@ OBJC_EXPORT id MKTAtLeast(NSUInteger minimumWantedNumberOfInvocations); #define atLeast(minimumWantedNumberOfInvocations) MKTAtLeast(minimumWantedNumberOfInvocations) #endif +/** + Verifies minimum number of invocations now, or at some time in the near future. + + The verification will succeed if the specified invocation happened the number of times + specified or more. + + Example: +@code +[verifyCount(mockObject, eventuallyAtLeast(2)) someMethod:@"some arg"]; +@endcode + + (In the event of a name clash, don't \#define @c MOCKITO_SHORTHAND and use the synonym + @c MKTEventuallyAtLeast instead.) + */ +#ifdef MOCKITO_SHORTHAND + #define eventuallyAtLeast(minimumWantedNumberOfInvocations) MKTEventuallyAtLeast(minimumWantedNumberOfInvocations) +#endif + OBJC_EXPORT id MKTAtLeastOnce(void); +OBJC_EXPORT id MKTEventuallyAtLeastOnce(void); /** Verifies that interaction happened once or more. @@ -216,3 +268,33 @@ OBJC_EXPORT id MKTAtLeastOnce(void); #ifdef MOCKITO_SHORTHAND #define atLeastOnce() MKTAtLeastOnce() #endif + +/** + Verifies that interaction happened once or more now, or at some time in the near future. + + Example: +@code +[verifyCount(mockObject, atLeastOnce()) someMethod:@"some arg"]; +@endcode + + (In the event of a name clash, don't \#define @c MOCKITO_SHORTHAND and use the synonym + @c MKTEventuallyAtLeastOnce instead.) + */ +#ifdef MOCKITO_SHORTHAND + #define eventuallyAtLeastOnce() MKTEventuallyAtLeastOnce() +#endif + + +/** + Retrieves the default timeout used by @c eventuallyTimes, @c eventuallyNever, + @c eventuallyAtLeast and @c eventuallyAtLeastOnce. + + The default value is 1 second. + */ +OBJC_EXPORT NSTimeInterval MKT_eventuallyDefaultTimeout(void); + +/** + Sets the default timeout used by @c eventuallyTimes, @c eventuallyNever, + @c eventuallyAtLeast and @c eventuallyAtLeastOnce. + */ +OBJC_EXPORT void MKT_setEventuallyDefaultTimeout(NSTimeInterval defaultTimeout); diff --git a/Source/OCMockito/OCMockito.m b/Source/OCMockito/OCMockito.m index 821855e5..2fd9c2a6 100644 --- a/Source/OCMockito/OCMockito.m +++ b/Source/OCMockito/OCMockito.m @@ -66,7 +66,12 @@ id MKTVerifyCountWithLocation(id mock, id mode, id testCase, const char *fileNam id MKTTimes(NSUInteger wantedNumberOfInvocations) { - return [MKTExactTimes timesWithCount:wantedNumberOfInvocations]; + return [MKTExactTimes timesWithCount:wantedNumberOfInvocations eventually:NO]; +} + +id MKTEventuallyTimes(NSUInteger wantedNumberOfInvocations) +{ + return [MKTExactTimes timesWithCount:wantedNumberOfInvocations eventually:YES]; } id MKTNever() @@ -74,12 +79,41 @@ id MKTNever() return MKTTimes(0); } +id MKTEventuallyNever() +{ + return MKTEventuallyTimes(0); +} + id MKTAtLeast(NSUInteger minimumWantedNumberOfInvocations) { - return [MKTAtLeastTimes timesWithMinimumCount:minimumWantedNumberOfInvocations]; + return [MKTAtLeastTimes timesWithMinimumCount:minimumWantedNumberOfInvocations eventually:NO]; +} + +id MKTEventuallyAtLeast(NSUInteger minimumWantedNumberOfInvocations) +{ + return [MKTAtLeastTimes timesWithMinimumCount:minimumWantedNumberOfInvocations eventually:YES]; } id MKTAtLeastOnce() { return MKTAtLeast(1); } + +id MKTEventuallyAtLeastOnce() +{ + return MKTEventuallyAtLeast(1); +} + + + +static NSTimeInterval sEventuallyDefaultTimeout = 1.0; // seconds + +NSTimeInterval MKT_eventuallyDefaultTimeout() +{ + return sEventuallyDefaultTimeout; +} + +void MKT_setEventuallyDefaultTimeout(NSTimeInterval defaultTimeout) +{ + sEventuallyDefaultTimeout = defaultTimeout; +} diff --git a/Source/Tests/MKTAtLeastTimesTest.m b/Source/Tests/MKTAtLeastTimesTest.m index 4dd66804..821b0898 100644 --- a/Source/Tests/MKTAtLeastTimesTest.m +++ b/Source/Tests/MKTAtLeastTimesTest.m @@ -33,6 +33,7 @@ @implementation MKTAtLeastTimesTest BOOL shouldPassAllExceptionsUp; MKTVerificationData *emptyData; NSInvocation *invocation; + NSTimeInterval timeout; } - (void)setUp @@ -42,10 +43,15 @@ - (void)setUp [emptyData setInvocations:[[MKTInvocationContainer alloc] init]]; [emptyData setWanted:[[MKTInvocationMatcher alloc] init]]; invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:"v@:"]]; + + // Reduce the timeout, so the test do not take that much + timeout = MKT_eventuallyDefaultTimeout(); + MKT_setEventuallyDefaultTimeout(0.1); } - (void)tearDown { + MKT_setEventuallyDefaultTimeout(timeout); emptyData = nil; [super tearDown]; } @@ -53,32 +59,57 @@ - (void)tearDown - (void)testVerificationShouldFailForEmptyDataIfCountIsNonzero { // given - MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1]; + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1 eventually:NO]; // when [[emptyData wanted] setExpectedInvocation:invocation]; // then - STAssertThrows([atLeastTimes verifyData:emptyData], @"verify should fail for empty data"); + STAssertThrows([atLeastTimes verifyData:emptyData], @"verify should throw an exception for empty data"); +} + +- (void)testVerificationShouldEventuallyFailForEmptyDataIfCountIsNonzero +{ + // given + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + + // then + STAssertThrows([atLeastTimes verifyData:emptyData], @"verify should eventually throw an exception for empty data"); } - (void)testVerificationShouldFailForTooLittleInvocations { // given - MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:2]; + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:2 eventually:NO]; // when [[emptyData wanted] setExpectedInvocation:invocation]; [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; // 1 call, but expect 2 // then - STAssertThrows([atLeastTimes verifyData:emptyData], @"verify should fail for too little invocations"); + STAssertThrows([atLeastTimes verifyData:emptyData], @"verify should throw an exception for too little invocations"); +} + +- (void)testVerificationShouldEventuallyFailForTooLittleInvocations +{ + // given + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:2 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; // 1 call, but expect 2 + + // then + STAssertThrows([atLeastTimes verifyData:emptyData], @"verify should eventually throw an exception for too little invocations"); } - (void)testVerificationShouldSucceedForMinimumCountZero { // given - MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:0]; + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:0 eventually:NO]; // when [[emptyData wanted] setExpectedInvocation:invocation]; @@ -87,10 +118,22 @@ - (void)testVerificationShouldSucceedForMinimumCountZero STAssertNoThrow([atLeastTimes verifyData:emptyData], @"verify should succeed for atLeast(0)"); } +- (void)testVerificationShouldEventuallySucceedForMinimumCountZero +{ + // given + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:0 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + + // then + STAssertNoThrow([atLeastTimes verifyData:emptyData], @"verify should eventually succeed for atLeast(0)"); +} + - (void)testVerificationShouldSucceedForExactNumberOfInvocations { // given - MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1]; + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1 eventually:NO]; // when [[emptyData wanted] setExpectedInvocation:invocation]; @@ -100,10 +143,39 @@ - (void)testVerificationShouldSucceedForExactNumberOfInvocations STAssertNoThrow([atLeastTimes verifyData:emptyData], @"verify should succeed for exact number of invocations matched"); } +- (void)testVerificationShouldEventuallySucceedForExactNumberOfInvocations +{ + // given + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; + + // then + STAssertNoThrow([atLeastTimes verifyData:emptyData], @"verify should eventually succeed for exact number of invocations matched"); +} + +- (void)testVerificationShouldAsynchronouslySucceedForExactNumberOfInvocations +{ + // given + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; + }); + + // then + STAssertNoThrow([atLeastTimes verifyData:emptyData], @"verify should eventually succeed for exact number of invocations matched"); +} + - (void)testVerificationShouldSucceedForMoreInvocations { // given - MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1]; + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1 eventually:NO]; // when [[emptyData wanted] setExpectedInvocation:invocation]; @@ -114,6 +186,20 @@ - (void)testVerificationShouldSucceedForMoreInvocations STAssertNoThrow([atLeastTimes verifyData:emptyData], @"verify should succeed for more invocations matched"); } +- (void)testVerificationShouldEventuallySucceedForMoreInvocations +{ + // given + MKTAtLeastTimes *atLeastTimes = [MKTAtLeastTimes timesWithMinimumCount:1 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; // 2 calls to the expected method + + // then + STAssertNoThrow([atLeastTimes verifyData:emptyData], @"verify should eventually succeed for more invocations matched"); +} + #pragma mark - Test From Top-Level @@ -129,6 +215,21 @@ - (void)testAtLeastInActionForExactCount [verifyCount(mockArray, atLeast(1)) removeAllObjects]; } +- (void)testEventuallyAtLeastInActionForExactCount +{ + // given + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [mockArray removeAllObjects]; + }); + + // then + [verifyCount(mockArray, eventuallyAtLeast(1)) removeAllObjects]; +} + - (void)testAtLeastOnceInActionForExactCount { // given @@ -141,6 +242,21 @@ - (void)testAtLeastOnceInActionForExactCount [verifyCount(mockArray, atLeastOnce()) removeAllObjects]; } +- (void)testEventuallyAtLeastOnceInActionForExactCount +{ + // given + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [mockArray removeAllObjects]; + }); + + // then + [verifyCount(mockArray, eventuallyAtLeastOnce()) removeAllObjects]; +} + - (void)testAtLeastInActionForExcessInvocations { // given @@ -155,6 +271,23 @@ - (void)testAtLeastInActionForExcessInvocations [verifyCount(mockArray, atLeast(2)) addObject:@"foo"]; } +- (void)testEventuallyAtLeastInActionForExcessInvocations +{ + // given + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [mockArray addObject:@"foo"]; + [mockArray addObject:@"foo"]; + [mockArray addObject:@"foo"]; + }); + + // then + [verifyCount(mockArray, eventuallyAtLeast(2)) addObject:@"foo"]; +} + - (void)testAtLeastOnceInActionForExcessInvocations { // given @@ -168,6 +301,21 @@ - (void)testAtLeastOnceInActionForExcessInvocations [verifyCount(mockArray, atLeastOnce()) addObject:@"foo"]; } +- (void)testEventuallyAtLeastOnceInActionForExcessInvocations +{ + // given + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [mockArray addObject:@"foo"]; + [mockArray addObject:@"foo"]; + }); + + // then + [verifyCount(mockArray, eventuallyAtLeastOnce()) addObject:@"foo"]; +} - (void)testAtLeastInActionForTooLittleInvocations { @@ -182,6 +330,22 @@ - (void)testAtLeastInActionForTooLittleInvocations STAssertThrows(([verifyCount(mockArray, atLeast(2)) addObject:@"foo"]), @"verifyCount() should have failed"); } +- (void)testEventuallyAtLeastInActionForTooLittleInvocations +{ + // given + [self disableFailureHandler]; // enable the handler to catch the exception generated by verify() + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 500*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [mockArray addObject:@"foo"]; + }); + + // then + STAssertThrows(([verifyCount(mockArray, eventuallyAtLeast(2)) addObject:@"foo"]), @"verifyCount() should have failed"); +} + #pragma mark - Helper Methods diff --git a/Source/Tests/MKTExactTimesTest.m b/Source/Tests/MKTExactTimesTest.m new file mode 100644 index 00000000..2166f388 --- /dev/null +++ b/Source/Tests/MKTExactTimesTest.m @@ -0,0 +1,364 @@ +// +// OCMockito - MKTExactTimesTest.m +// Copyright 2013 Jonathan M. Reid. See LICENSE.txt +// +// Created by Daniel Rodríguez Troitiño on 30.08.13. +// Source: https://github.com/jonreid/OCMockito +// + +#import "MKTExactTimes.h" + +#define MOCKITO_SHORTHAND +#import "OCMockito.h" +#import "MKTInvocationContainer.h" +#import "MKTInvocationMatcher.h" +#import "MKTVerificationData.h" + + // Test support +#import + +#define HC_SHORTHAND +#if TARGET_OS_MAC +#import +#else +#import +#endif + + +@interface MKTExactTimesTest : SenTestCase +@end + +@implementation MKTExactTimesTest +{ + BOOL shouldPassAllExceptionsUp; + MKTVerificationData *emptyData; + NSInvocation *invocation; + NSTimeInterval timeout; +} + +- (void)setUp +{ + [super setUp]; + emptyData = [[MKTVerificationData alloc] init]; + [emptyData setInvocations:[[MKTInvocationContainer alloc] init]]; + [emptyData setWanted:[[MKTInvocationMatcher alloc] init]]; + invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:"v@:"]]; + + // Reduce the timeout, so the test do not take that much + timeout = MKT_eventuallyDefaultTimeout(); + MKT_setEventuallyDefaultTimeout(0.1); +} + +- (void)tearDown +{ + MKT_setEventuallyDefaultTimeout(timeout); + emptyData = nil; + [super tearDown]; +} + +- (void)testVerificationShouldFailForEmptyDataIfCountIsNonzero +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:1 eventually:NO]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + + // then + STAssertThrows([exactTimes verifyData:emptyData], @"verify should throw an exception for empty data"); +} + +- (void)testVerificationShouldEventuallyFailForEmptyDataIfCountIsNonzero +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:1 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + + // then + STAssertThrows([exactTimes verifyData:emptyData], @"verify should eventually throw an exception for empty data"); +} + +- (void)testVerificationShouldFailForTooLittleInvocations +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:2 eventually:NO]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; // 1 call, but expect 2 + + // then + STAssertThrows([exactTimes verifyData:emptyData], @"verify should throw an exception for too little invocations"); +} + +- (void)testVerificationShouldEventuallyFailForTooLittleInvocations +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:2 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; // 1 call, but expect 2 + + // then + STAssertThrows([exactTimes verifyData:emptyData], @"verify should eventually throw an exception for too little invocations"); +} + +- (void)testVerificationShouldSucceedForMinimumCountZero +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:0 eventually:NO]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + + // then + STAssertNoThrow([exactTimes verifyData:emptyData], @"verify should succeed for times(0)"); +} + +- (void)testVerificationShouldEventuallySucceedForMinimumCountZero +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:0 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + + // then + STAssertNoThrow([exactTimes verifyData:emptyData], @"verify should eventually succeed for eventuallyTimes(0)"); +} + +- (void)testVerificationShouldSucceedForExactNumberOfInvocations +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:1 eventually:NO]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; + + // then + STAssertNoThrow([exactTimes verifyData:emptyData], @"verify should succeed for exact number of invocations matched"); +} + +- (void)testVerificationShouldEventuallySucceedForExactNumberOfInvocations +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:1 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; + + // then + STAssertNoThrow([exactTimes verifyData:emptyData], @"verify should eventually succeed for exact number of invocations matched"); +} + +- (void)testVerificationShouldAsynchronouslySucceedForExactNumberOfInvocations +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:1 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; + }); + + // then + STAssertNoThrow([exactTimes verifyData:emptyData], @"verify should eventually succeed for exact number of invocations matched"); +} + +- (void)testVerificationShouldFailForMoreInvocations +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:1 eventually:NO]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; // 2 calls to the expected method + + // then + STAssertThrows([exactTimes verifyData:emptyData], @"verify should throw an exception for more invocations matched"); +} + +- (void)testVerificationShouldEventuallyFailForMoreInvocations +{ + // given + MKTExactTimes *exactTimes = [MKTExactTimes timesWithCount:1 eventually:YES]; + + // when + [[emptyData wanted] setExpectedInvocation:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; + [[emptyData invocations] setInvocationForPotentialStubbing:invocation]; // 2 calls to the expected method + + // then + STAssertThrows([exactTimes verifyData:emptyData], @"verify should eventually throw an exception for more invocations matched"); +} + + +#pragma mark - Test From Top-Level + +- (void)testTimesInActionForExactCount +{ + // given + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + [mockArray removeAllObjects]; + + // then + [verifyCount(mockArray, times(1)) removeAllObjects]; +} + +- (void)testEventuallyTimesInActionForExactCount +{ + // given + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [mockArray removeAllObjects]; + }); + + // then + [verifyCount(mockArray, eventuallyTimes(1)) removeAllObjects]; +} + +- (void)testNeverInActionForExactCount +{ + // given + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + // nothing + + // then + [verifyCount(mockArray, never()) removeAllObjects]; +} + +- (void)testEventuallyNeverInActionForExactCount +{ + // given + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + // nothing + + // then + [verifyCount(mockArray, eventuallyNever()) removeAllObjects]; +} + +- (void)testTimesInActionForExcessInvocations +{ + // given + [self disableFailureHandler]; // enable the handler to catch the exception generated by verify() + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + [mockArray addObject:@"foo"]; + [mockArray addObject:@"foo"]; + [mockArray addObject:@"foo"]; + + // then + STAssertThrows(([verifyCount(mockArray, times(2)) addObject:@"foo"]), @"verifyCount() should have failed"); +} + +- (void)testEventuallyTimesInActionForExcessInvocations +{ + // given + [self disableFailureHandler]; // enable the handler to catch the exception generated by verify() + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 500*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [mockArray addObject:@"foo"]; + [mockArray addObject:@"foo"]; + [mockArray addObject:@"foo"]; + }); + + // then + STAssertThrows(([verifyCount(mockArray, eventuallyTimes(2)) addObject:@"foo"]), @"verifyCount() should have failed"); +} + +- (void)testNeverInActionForExcessInvocations +{ + // given + [self disableFailureHandler]; // enable the handler to catch the exception generated by verify() + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + [mockArray addObject:@"foo"]; + + // then + STAssertThrows(([verifyCount(mockArray, never()) addObject:@"foo"]), @"verifyCount() should have failed"); +} + +- (void)testEventuallyNeverInActionForExcessInvocations +{ + // given + [self disableFailureHandler]; // enable the handler to catch the exception generated by verify() + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [mockArray addObject:@"foo"]; + }); + + // then + STAssertThrows(([verifyCount(mockArray, eventuallyNever()) addObject:@"foo"]), @"verifyCount() should have failed"); +} + +- (void)testTimesInActionForTooLittleInvocations +{ + // given + [self disableFailureHandler]; // enable the handler to catch the exception generated by verify() + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + [mockArray addObject:@"foo"]; + + // then + STAssertThrows(([verifyCount(mockArray, times(2)) addObject:@"foo"]), @"verifyCount() should have failed"); +} + +- (void)testEventuallyTimesInActionForTooLittleInvocations +{ + // given + [self disableFailureHandler]; // enable the handler to catch the exception generated by verify() + NSMutableArray *mockArray = mock([NSMutableArray class]); + + // when + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 500*NSEC_PER_MSEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [mockArray addObject:@"foo"]; + }); + + // then + STAssertThrows(([verifyCount(mockArray, eventuallyTimes(2)) addObject:@"foo"]), @"verifyCount() should have failed"); +} + + +#pragma mark - Helper Methods + +- (void)disableFailureHandler +{ + shouldPassAllExceptionsUp = YES; +} + +- (void)failWithException:(NSException *)exception +{ + if (shouldPassAllExceptionsUp) + @throw exception; + else + [super failWithException:exception]; +} + +@end diff --git a/Source/Tests/MKTMockingProgressTest.m b/Source/Tests/MKTMockingProgressTest.m index 37bb7631..65676dea 100644 --- a/Source/Tests/MKTMockingProgressTest.m +++ b/Source/Tests/MKTMockingProgressTest.m @@ -84,7 +84,7 @@ - (void)testPullVerificationModeWithoutVerificationStartedShouldReturnNil - (void)testPullVerificationModeWithVerificationStartedShouldReturnMode { // given - id mode = [MKTExactTimes timesWithCount:42]; + id mode = [MKTExactTimes timesWithCount:42 eventually:NO]; // when [mockingProgress verificationStarted:mode atLocation:MKTTestLocationMake(self, __FILE__, __LINE__)]; @@ -96,7 +96,7 @@ - (void)testPullVerificationModeWithVerificationStartedShouldReturnMode - (void)testPullVerificationModeShouldClearCurrentVerification { // given - id mode = [MKTExactTimes timesWithCount:42]; + id mode = [MKTExactTimes timesWithCount:42 eventually:NO]; // when [mockingProgress verificationStarted:mode atLocation:MKTTestLocationMake(self, __FILE__, __LINE__)];