KWHaveMatcher.m 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. //
  2. // Licensed under the terms in License.txt
  3. //
  4. // Copyright 2010 Allen Ding. All rights reserved.
  5. //
  6. #import "KWHaveMatcher.h"
  7. #import "KWCountType.h"
  8. #import "KWFormatter.h"
  9. #import "KWInvocationCapturer.h"
  10. #import "KWObjCUtilities.h"
  11. #import "KWStringUtilities.h"
  12. static NSString * const MatchVerifierKey = @"MatchVerifierKey";
  13. static NSString * const CountTypeKey = @"CountTypeKey";
  14. static NSString * const CountKey = @"CountKey";
  15. @interface KWHaveMatcher()
  16. #pragma mark - Properties
  17. @property (nonatomic, assign) KWCountType countType;
  18. @property (nonatomic, assign) NSUInteger count;
  19. @property (nonatomic, strong) NSInvocation *invocation;
  20. @property (nonatomic, assign) NSUInteger actualCount;
  21. @end
  22. @implementation KWHaveMatcher
  23. #pragma mark - Getting Matcher Strings
  24. + (NSArray *)matcherStrings {
  25. return @[
  26. @"haveCountOf:",
  27. @"haveCountOfAtLeast:",
  28. @"haveCountOfAtMost:",
  29. @"haveLengthOf:",
  30. @"haveLengthOfAtLeast:",
  31. @"haveLengthOfAtMost:",
  32. @"have:itemsForInvocation:",
  33. @"haveAtLeast:itemsForInvocation:",
  34. @"haveAtMost:itemsForInvocation:",
  35. ];
  36. }
  37. #pragma mark - Matching
  38. - (id)targetObject {
  39. if (self.invocation == nil)
  40. return self.subject;
  41. SEL selector = [self.invocation selector];
  42. if ([self.subject respondsToSelector:selector]) {
  43. NSMethodSignature *signature = [self.subject methodSignatureForSelector:selector];
  44. if (!KWObjCTypeIsObject([signature methodReturnType]))
  45. [NSException raise:@"KWMatcherEception" format:@"a valid collection was not specified"];
  46. __unsafe_unretained id object = nil;
  47. [self.invocation invokeWithTarget:self.subject];
  48. [self.invocation getReturnValue:&object];
  49. return object;
  50. } else if (KWSelectorParameterCount(selector) == 0) {
  51. return self.subject;
  52. } else {
  53. return nil;
  54. }
  55. }
  56. - (BOOL)evaluate {
  57. id targetObject = [self targetObject];
  58. if ([targetObject respondsToSelector:@selector(count)])
  59. self.actualCount = [targetObject count];
  60. else if ([targetObject respondsToSelector:@selector(length)])
  61. self.actualCount = [targetObject length];
  62. else
  63. self.actualCount = 0;
  64. switch (self.countType) {
  65. case KWCountTypeExact:
  66. return self.actualCount == self.count;
  67. case KWCountTypeAtLeast:
  68. return self.actualCount >= self.count;
  69. case KWCountTypeAtMost:
  70. return self.actualCount <= self.count;
  71. }
  72. assert(0 && "should never reach here");
  73. return NO;
  74. }
  75. #pragma mark - Getting Failure Messages
  76. - (NSString *)verbPhrase {
  77. switch (self.countType) {
  78. case KWCountTypeExact:
  79. return @"have";
  80. case KWCountTypeAtLeast:
  81. return @"have at least";
  82. case KWCountTypeAtMost:
  83. return @"have at most";
  84. }
  85. assert(0 && "should never reach here");
  86. return nil;
  87. }
  88. - (NSString *)itemPhrase {
  89. if (self.invocation == nil)
  90. return @"items";
  91. else
  92. return NSStringFromSelector([self.invocation selector]);
  93. }
  94. - (NSString *)actualCountPhrase {
  95. if (self.actualCount == 1)
  96. return @"1 item";
  97. else
  98. return [NSString stringWithFormat:@"%u items", (unsigned)self.actualCount];
  99. }
  100. - (NSString *)failureMessageForShould {
  101. return [NSString stringWithFormat:@"expected subject to %@ %u %@, got %@",
  102. [self verbPhrase],
  103. (unsigned)self.count,
  104. [self itemPhrase],
  105. [self actualCountPhrase]];
  106. }
  107. - (NSString *)failureMessageForShouldNot {
  108. return [NSString stringWithFormat:@"expected subject not to %@ %u %@",
  109. [self verbPhrase],
  110. (unsigned)self.count,
  111. [self itemPhrase]];
  112. }
  113. #pragma mark - Description
  114. - (NSString *)description {
  115. return [NSString stringWithFormat:@"%@ %u %@", [self verbPhrase], (unsigned)self.count, [self itemPhrase]];
  116. }
  117. #pragma mark - Configuring Matchers
  118. - (void)haveCountOf:(NSUInteger)aCount {
  119. self.count = aCount;
  120. self.countType = KWCountTypeExact;
  121. }
  122. - (void)haveLengthOf:(NSUInteger)aCount {
  123. [self haveCountOf:aCount];
  124. }
  125. - (void)haveCountOfAtLeast:(NSUInteger)aCount {
  126. self.count = aCount;
  127. self.countType = KWCountTypeAtLeast;
  128. }
  129. - (void)haveLengthOfAtLeast:(NSUInteger)aCount {
  130. [self haveCountOfAtLeast:aCount];
  131. }
  132. - (void)haveCountOfAtMost:(NSUInteger)aCount {
  133. self.count = aCount;
  134. self.countType = KWCountTypeAtMost;
  135. }
  136. - (void)haveLengthOfAtMost:(NSUInteger)aCount {
  137. [self haveCountOfAtMost:aCount];
  138. }
  139. - (void)have:(NSUInteger)aCount itemsForInvocation:(NSInvocation *)anInvocation {
  140. self.count = aCount;
  141. self.countType = KWCountTypeExact;
  142. self.invocation = anInvocation;
  143. }
  144. - (void)haveAtLeast:(NSUInteger)aCount itemsForInvocation:(NSInvocation *)anInvocation {
  145. self.count = aCount;
  146. self.countType = KWCountTypeAtLeast;
  147. self.invocation = anInvocation;
  148. }
  149. - (void)haveAtMost:(NSUInteger)aCount itemsForInvocation:(NSInvocation *)anInvocation {
  150. self.count = aCount;
  151. self.countType = KWCountTypeAtMost;
  152. self.invocation = anInvocation;
  153. }
  154. #pragma mark - Capturing Invocations
  155. + (NSMethodSignature *)invocationCapturer:(KWInvocationCapturer *)anInvocationCapturer methodSignatureForSelector:(SEL)aSelector {
  156. KWMatchVerifier *verifier = (anInvocationCapturer.userInfo)[MatchVerifierKey];
  157. if ([verifier.subject respondsToSelector:aSelector])
  158. return [verifier.subject methodSignatureForSelector:aSelector];
  159. // Arbitrary selectors are allowed as expectation expression terminals when
  160. // the subject itself is a collection, so return a dummy method signature.
  161. NSString *encoding = KWEncodingForDefaultMethod();
  162. return [NSMethodSignature signatureWithObjCTypes:[encoding UTF8String]];
  163. }
  164. + (void)invocationCapturer:(KWInvocationCapturer *)anInvocationCapturer didCaptureInvocation:(NSInvocation *)anInvocation {
  165. NSDictionary *userInfo = anInvocationCapturer.userInfo;
  166. id verifier = userInfo[MatchVerifierKey];
  167. KWCountType countType = [userInfo[CountTypeKey] unsignedIntegerValue];
  168. NSUInteger count = [userInfo[CountKey] unsignedIntegerValue];
  169. switch (countType) {
  170. case KWCountTypeExact:
  171. [verifier have:count itemsForInvocation:anInvocation];
  172. break;
  173. case KWCountTypeAtLeast:
  174. [verifier haveAtLeast:count itemsForInvocation:anInvocation];
  175. break;
  176. case KWCountTypeAtMost:
  177. [verifier haveAtMost:count itemsForInvocation:anInvocation];
  178. break;
  179. }
  180. }
  181. @end
  182. #pragma mark - Verifying
  183. @implementation KWMatchVerifier(KWHaveMatcherAdditions)
  184. #pragma mark - Invocation Capturing Methods
  185. - (NSDictionary *)userInfoForHaveMatcherWithCountType:(KWCountType)aCountType count:(NSUInteger)aCount {
  186. return @{MatchVerifierKey: self,
  187. CountTypeKey: @(aCountType),
  188. CountKey: @(aCount)};
  189. }
  190. - (id)have:(NSUInteger)aCount {
  191. NSDictionary *userInfo = [self userInfoForHaveMatcherWithCountType:KWCountTypeExact count:aCount];
  192. return [KWInvocationCapturer invocationCapturerWithDelegate:[KWHaveMatcher class] userInfo:userInfo];
  193. }
  194. - (id)haveAtLeast:(NSUInteger)aCount {
  195. NSDictionary *userInfo = [self userInfoForHaveMatcherWithCountType:KWCountTypeAtLeast count:aCount];
  196. return [KWInvocationCapturer invocationCapturerWithDelegate:[KWHaveMatcher class] userInfo:userInfo];
  197. }
  198. - (id)haveAtMost:(NSUInteger)aCount {
  199. NSDictionary *userInfo = [self userInfoForHaveMatcherWithCountType:KWCountTypeAtMost count:aCount];
  200. return [KWInvocationCapturer invocationCapturerWithDelegate:[KWHaveMatcher class] userInfo:userInfo];
  201. }
  202. @end