diff --git a/Examples/ExampleTestCase.m b/Examples/ExampleTestCase.m index 391cd61f..d35e9618 100644 --- a/Examples/ExampleTestCase.m +++ b/Examples/ExampleTestCase.m @@ -156,6 +156,14 @@ - (void)itShouldVerifyReceivedMessages { [[theValue(energyLevel) should] equal:theValue(1.01f)]; } +- (void)itShouldVerifyReceivedMessagesWithAnyArgument { + id subject = [Cruiser cruiser]; + [[subject should] receive:@selector(energyLevelInWarpCore:) andReturn:theValue(1.01f) withCount:2 arguments:any()]; + [subject energyLevelInWarpCore:2]; + float energyLevel = [subject energyLevelInWarpCore:2]; + [[theValue(energyLevel) should] equal:theValue(1.01f)]; +} + - (void)itShouldAllowExpectationsArgumentsToBeHamcrestMatchersForFuzzyMatching { id subject = [Robot robot]; diff --git a/Kiwi.xcodeproj/project.pbxproj b/Kiwi.xcodeproj/project.pbxproj index 617b1be5..e908d13e 100755 --- a/Kiwi.xcodeproj/project.pbxproj +++ b/Kiwi.xcodeproj/project.pbxproj @@ -126,6 +126,7 @@ A3EB8064131EA572001860F5 /* KWDeviceInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = F5D7C8CA11643B9700758FEA /* KWDeviceInfo.m */; }; A3EB8065131EA574001860F5 /* KWHamrestMatchingAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A352EA0D12EDC6F20049C691 /* KWHamrestMatchingAdditions.m */; }; A3FABFAF13CBB187002003F7 /* KiwiBlockMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = A3FABFAE13CBB187002003F7 /* KiwiBlockMacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ADBF4B031511C6B300784E9E /* KWAny.h in Headers */ = {isa = PBXBuildFile; fileRef = F5FC83B611B100B100BF98A7 /* KWAny.h */; settings = {ATTRIBUTES = (Public, ); }; }; F5015BB911583ABD002E9A98 /* KWCallSite.m in Sources */ = {isa = PBXBuildFile; fileRef = F5015B581158398E002E9A98 /* KWCallSite.m */; }; F5015BBA11583ABD002E9A98 /* KWContainMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = F5015B5A1158398E002E9A98 /* KWContainMatcher.m */; }; F5015BBB11583ABD002E9A98 /* KWEqualMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = F5015B5C1158398E002E9A98 /* KWEqualMatcher.m */; }; @@ -218,6 +219,7 @@ F5F7471011B328F1000CA15C /* TestReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = F5F7470F11B328F1000CA15C /* TestReporter.m */; }; F5FC83A811B1000500BF98A2 /* KWStringUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = F5FC83A611B1000500BF98A2 /* KWStringUtilities.m */; }; F5FC83B611B100B100BF98A2 /* KWStringUtilitiesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F5FC83B511B100B100BF98A2 /* KWStringUtilitiesTest.m */; }; + F5FC83B611B100B100BF98A4 /* KWAny.m in Sources */ = {isa = PBXBuildFile; fileRef = F5FC83B611B100B100BF98A3 /* KWAny.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -471,6 +473,8 @@ F5FC83A511B1000500BF98A2 /* KWStringUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KWStringUtilities.h; sourceTree = ""; }; F5FC83A611B1000500BF98A2 /* KWStringUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KWStringUtilities.m; sourceTree = ""; }; F5FC83B511B100B100BF98A2 /* KWStringUtilitiesTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KWStringUtilitiesTest.m; sourceTree = ""; }; + F5FC83B611B100B100BF98A3 /* KWAny.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KWAny.m; sourceTree = ""; }; + F5FC83B611B100B100BF98A7 /* KWAny.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KWAny.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -726,6 +730,8 @@ F57A6868115A3A1D00591C2A /* Support */ = { isa = PBXGroup; children = ( + F5FC83B611B100B100BF98A3 /* KWAny.m */, + F5FC83B611B100B100BF98A7 /* KWAny.h */, F5E9FC2F11AE6A0F0089BACE /* KWBlock.h */, F5E9FC3011AE6A0F0089BACE /* KWBlock.m */, F5015B571158398E002E9A98 /* KWCallSite.h */, @@ -992,6 +998,7 @@ A34FADAB13BBF4A4003968B2 /* KiwiConfiguration.h in Headers */, A34FADAC13BBF4A4003968B2 /* KiwiMacros.h in Headers */, A3FABFAF13CBB187002003F7 /* KiwiBlockMacros.h in Headers */, + ADBF4B031511C6B300784E9E /* KWAny.h in Headers */, A3CB75E2144C3479002D1F7A /* KWExampleSuite.h in Headers */, 4B9D040814D3EE7300707E83 /* KWExampleGroupDelegate.h in Headers */, ); @@ -1228,6 +1235,7 @@ A385CAEC13AA7EDD00DCA951 /* KWUserDefinedMatcher.m in Sources */, A385CAF013AAC9B800DCA951 /* KWMatchers.m in Sources */, A3CB75E3144C3479002D1F7A /* KWExampleSuite.m in Sources */, + F5FC83B611B100B100BF98A4 /* KWAny.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Kiwi/KWAny.h b/Kiwi/KWAny.h new file mode 100644 index 00000000..c6c0c9ae --- /dev/null +++ b/Kiwi/KWAny.h @@ -0,0 +1,18 @@ +// +// Licensed under the terms in License.txt +// +// Copyright 2010 Allen Ding. All rights reserved. +// + +#import "KiwiConfiguration.h" + +// KWAny exists to determine arguments in a message pattern that should +// match any value. Used for pointers as well as for scalar values. +@interface KWAny : NSObject + +#pragma mark - +#pragma mark Initializing + ++ (id)any; + +@end \ No newline at end of file diff --git a/Kiwi/KWAny.m b/Kiwi/KWAny.m new file mode 100644 index 00000000..6dbf0998 --- /dev/null +++ b/Kiwi/KWAny.m @@ -0,0 +1,47 @@ +// +// Licensed under the terms in License.txt +// +// Copyright 2010 Allen Ding. All rights reserved. +// + +#import "KWAny.h" + +@implementation KWAny + +#pragma mark - +#pragma mark Initializing + +static KWAny *sharedAny = nil; + ++ (id)any { + if (sharedAny == nil) { + sharedAny = [[super allocWithZone:nil] init]; + } + + return sharedAny; +} + ++ (id)allocWithZone:(NSZone *)zone { + return [[self any] retain]; +} + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +- (id)retain { + return self; +} + +- (NSUInteger)retainCount { + return NSUIntegerMax; +} + +- (oneway void)release { +} + +- (id)autorelease { + return self; +} + +@end diff --git a/Kiwi/KWMessagePattern.m b/Kiwi/KWMessagePattern.m index df756dc6..ca6d144c 100644 --- a/Kiwi/KWMessagePattern.m +++ b/Kiwi/KWMessagePattern.m @@ -12,6 +12,7 @@ #import "NSInvocation+KiwiAdditions.h" #import "NSMethodSignature+KiwiAdditions.h" #import "KWHCMatcher.h" +#import "Kiwi.h" @implementation KWMessagePattern @@ -130,6 +131,10 @@ - (BOOL)argumentFiltersMatchInvocationArguments:(NSInvocation *)anInvocation { // Match argument filter to object id argumentFilter = [self.argumentFilters objectAtIndex:i]; + if ([argumentFilter isEqual:[KWAny any]]) { + continue; + } + if (KWObjCTypeIsObject(objCType)) { if ([argumentFilter isEqual:[KWNull null]]) { if (object != nil) diff --git a/Kiwi/Kiwi.h b/Kiwi/Kiwi.h index 2abb75e7..64d4a915 100644 --- a/Kiwi/Kiwi.h +++ b/Kiwi/Kiwi.h @@ -14,6 +14,7 @@ extern "C" { #import "KWAfterAllNode.h" #import "KWAfterEachNode.h" +#import "KWAny.h" #import "KWAsyncVerifier.h" #import "KWBeBetweenMatcher.h" #import "KWBeEmptyMatcher.h" diff --git a/Kiwi/KiwiMacros.h b/Kiwi/KiwiMacros.h index afbbc9ab..821de2f4 100644 --- a/Kiwi/KiwiMacros.h +++ b/Kiwi/KiwiMacros.h @@ -33,6 +33,9 @@ #define theReturnValueOfBlock(block) [KWFutureObject futureObjectWithBlock:block] // DEPRECATED #define expectFutureValue(futureValue) [KWFutureObject futureObjectWithBlock:^{ return futureValue; }] +// used for message patterns to allow matching any value +#define any() [KWAny any] + // If a gcc compatible compiler is available, use the statement and // declarations in expression extension to provide a convenient catch-all macro // to create KWValues. diff --git a/Tests/KWMessagePatternTest.m b/Tests/KWMessagePatternTest.m index 43e31a95..b541d15b 100644 --- a/Tests/KWMessagePatternTest.m +++ b/Tests/KWMessagePatternTest.m @@ -7,7 +7,6 @@ #import "Kiwi.h" #import "KiwiTestConfiguration.h" #import "NSInvocation+KiwiAdditions.h" -#import "TestClasses.h" #if KW_TESTS_ENABLED @@ -48,6 +47,38 @@ - (void)testItShouldMatchInvocationsWithNilArguments { STAssertTrue([messagePattern matchesInvocation:invocation], @"expected matching invocation"); } +- (void)testItShouldMatchInvocationsWithAnyArguments { + KWMessagePattern *messagePattern = [self messagePatternWithSelector:@selector(addObserver:forKeyPath:options:context:) arguments:@"foo", + [KWAny any], + [KWAny any], + nil]; + NSMethodSignature *signature = [NSObject instanceMethodSignatureForSelector:@selector(addObserver:forKeyPath:options:context:)]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setSelector:@selector(addObserver:forKeyPath:options:context:)]; + id observer = @"foo"; + id keyPath = @"bar"; + NSKeyValueObservingOptions options = 1; + void *context = nil; + [invocation setMessageArguments:&observer, &keyPath, &options, &context]; + STAssertTrue([messagePattern matchesInvocation:invocation], @"expected matching invocation"); +} + +- (void)testItShouldNotMatchInvocationsWithAnyArguments { + KWMessagePattern *messagePattern = [self messagePatternWithSelector:@selector(addObserver:forKeyPath:options:context:) arguments:@"foo", + [KWAny any], + [KWValue valueWithUnsignedInt:0], + nil]; + NSMethodSignature *signature = [NSObject instanceMethodSignatureForSelector:@selector(addObserver:forKeyPath:options:context:)]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setSelector:@selector(addObserver:forKeyPath:options:context:)]; + id observer = @"foo"; + id keyPath = @"bar"; + NSKeyValueObservingOptions options = 1; + void *context = nil; + [invocation setMessageArguments:&observer, &keyPath, &options, &context]; + STAssertTrue(![messagePattern matchesInvocation:invocation], @"expected non-matching invocation"); +} + - (void)testItShouldNotMatchInvocationsWithDifferentArguments { KWMessagePattern *messagePattern = [self messagePatternWithSelector:@selector(addObserver:forKeyPath:options:context:) arguments:@"foo", nil, diff --git a/Tests/KWMockTest.m b/Tests/KWMockTest.m index 161ed2a2..ed23c36e 100644 --- a/Tests/KWMockTest.m +++ b/Tests/KWMockTest.m @@ -70,6 +70,13 @@ - (void)testItShouldStubWithASelectorReturnValueAndArguments { STAssertTrue([mock energyLevelInWarpCore:2] != 30.0f, @"expected method with arguments not to be stubbed"); } +- (void)testItShouldStubWithASelectorReturnValueAndAnyArguments { + id mock = [Cruiser nullMock]; + [mock stub:@selector(energyLevelInWarpCore:) andReturn:theValue(30.0f) withArguments:any()]; + STAssertEquals([mock energyLevelInWarpCore:3], 30.0f, @"expected method with any() arguments to be stubbed"); + STAssertEquals([mock energyLevelInWarpCore:2], 30.0f, @"expected method with any() arguments to be stubbed"); +} + - (void)testItShouldStubWithAMessage { id mock = [Cruiser mock]; STAssertNoThrow([[mock stub] energyLevelInWarpCore:3], @"expected mock to stub message"); diff --git a/Tests/KWRealObjectStubTest.m b/Tests/KWRealObjectStubTest.m index 2244f670..998baf41 100644 --- a/Tests/KWRealObjectStubTest.m +++ b/Tests/KWRealObjectStubTest.m @@ -47,6 +47,13 @@ - (void)testItShouldStubInstanceMethodsReturningObjects { STAssertEquals(fighter, [cruiser fighterWithCallsign:fighterCallsign], @"expected method to be stubbed"); } +- (void)testItShouldStubInstanceMethodsReturningObjectsWithAnyArguments { + Cruiser *cruiser = [Cruiser cruiserWithCallsign:@"Galactica"]; + Fighter *fighter = [Fighter fighterWithCallsign:@"Viper 1"]; + [cruiser stub:@selector(fighterWithCallsign:) andReturn:fighter withArguments:any()]; + STAssertEquals(fighter, [cruiser fighterWithCallsign:@"Foo"], @"expected method to be stubbed"); +} + - (void)testItShouldClearStubbedRecursiveMethods { NSUInteger starHash = 8 + 4 + 2 + 1; Cruiser *cruiser = [Cruiser cruiserWithCallsign:@"Galactica"];