main
  1//
  2// Licensed under the terms in License.txt
  3//
  4// Copyright 2010 Allen Ding. All rights reserved.
  5//
  6
  7#import "KWMock.h"
  8#import <objc/runtime.h>
  9#import "KWFormatter.h"
 10#import "KWMessagePattern.h"
 11#import "KWMessageSpying.h"
 12#import "KWStringUtilities.h"
 13#import "KWStub.h"
 14#import "KWWorkarounds.h"
 15#import "NSInvocation+KiwiAdditions.h"
 16#import "KWCaptureSpy.h"
 17
 18static NSString * const ExpectOrStubTagKey = @"ExpectOrStubTagKey";
 19static NSString * const StubTag = @"StubTag";
 20static NSString * const ExpectTag = @"ExpectTag";
 21static NSString * const StubValueKey = @"StubValueKey";
 22static NSString * const StubSecondValueKey = @"StubSecondValueKey";
 23static NSString * const ChangeStubValueAfterTimesKey = @"ChangeStubValueAfterTimesKey";
 24
 25@interface KWMock()
 26
 27#pragma mark -
 28#pragma mark Initializing
 29
 30- (id)initAsNullMock:(BOOL)nullMockFlag withName:(NSString *)aName forClass:(Class)aClass protocol:(Protocol *)aProtocol;
 31
 32#pragma mark -
 33#pragma mark Properties
 34
 35@property (nonatomic, readonly) NSMutableArray *stubs;
 36@property (nonatomic, readonly) NSMutableArray *expectedMessagePatterns;
 37@property (nonatomic, readonly) NSMutableDictionary *messageSpies;
 38
 39
 40#pragma mark -
 41#pragma mark Handling Invocations
 42
 43- (BOOL)processReceivedInvocation:(NSInvocation *)invocation;
 44
 45@end
 46
 47@implementation KWMock
 48
 49#pragma mark -
 50#pragma mark Initializing
 51
 52- (id)init {
 53    // May already have been initialized since stubbing -init is allowed!
 54    if (self.stubs != nil) {
 55        KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:_cmd];
 56        [self expectMessagePattern:messagePattern];
 57        NSInvocation *invocation = [NSInvocation invocationWithTarget:self selector:_cmd];
 58
 59        if ([self processReceivedInvocation:invocation]) {
 60            id result = nil;
 61            [invocation getReturnValue:&result];
 62            return result;
 63        } else {
 64            return self;
 65        }
 66    }
 67
 68    return [self initAsNullMock:NO withName:nil forClass:nil protocol:nil];
 69}
 70
 71- (id)initForClass:(Class)aClass {
 72    return [self initAsNullMock:NO withName:nil forClass:aClass protocol:nil];
 73}
 74
 75- (id)initForProtocol:(Protocol *)aProtocol {
 76    return [self initAsNullMock:NO withName:nil forClass:nil protocol:aProtocol];
 77}
 78
 79- (id)initWithName:(NSString *)aName forClass:(Class)aClass {
 80    return [self initAsNullMock:NO withName:aName forClass:aClass protocol:nil];
 81}
 82
 83- (id)initWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol {
 84    return [self initAsNullMock:NO withName:aName forClass:nil protocol:aProtocol];
 85}
 86
 87- (id)initAsNullMockForClass:(Class)aClass {
 88    return [self initAsNullMock:YES withName:nil forClass:aClass protocol:nil];
 89}
 90
 91- (id)initAsNullMockForProtocol:(Protocol *)aProtocol {
 92    return [self initAsNullMock:YES withName:nil forClass:nil protocol:aProtocol];
 93}
 94
 95- (id)initAsNullMockWithName:(NSString *)aName forClass:(Class)aClass {
 96    return [self initAsNullMock:YES withName:aName forClass:aClass protocol:nil];
 97}
 98
 99- (id)initAsNullMockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol {
100    return [self initAsNullMock:YES withName:aName forClass:nil protocol:aProtocol];
101}
102
103- (id)initAsNullMock:(BOOL)nullMockFlag withName:(NSString *)aName forClass:(Class)aClass protocol:(Protocol *)aProtocol {
104    if ((self = [super init])) {
105        isNullMock = nullMockFlag;
106        mockName = [aName copy];
107        mockedClass = aClass;
108        mockedProtocol = aProtocol;
109        stubs = [[NSMutableArray alloc] init];
110        expectedMessagePatterns = [[NSMutableArray alloc] init];
111        messageSpies = [[NSMutableDictionary alloc] init];
112    }
113
114    return self;
115}
116
117- (id)initAsPartialMockForObject:(id)object {
118    return [self initAsPartialMockWithName:nil forObject:object];
119}
120
121- (id)initAsPartialMockWithName:(NSString *)aName forObject:(id)object {
122    if ((self = [self initAsNullMock:YES withName:aName forClass:[object class] protocol:nil])) {
123        isPartialMock = YES;
124        mockedObject = [object retain];
125    }
126    return self;
127}
128
129+ (id)mockForClass:(Class)aClass {
130    return [[[self alloc] initForClass:aClass] autorelease];
131}
132
133+ (id)mockForProtocol:(Protocol *)aProtocol {
134    return [[[self alloc] initForProtocol:aProtocol] autorelease];
135}
136
137+ (id)mockWithName:(NSString *)aName forClass:(Class)aClass {
138    return [[[self alloc] initWithName:aName forClass:aClass] autorelease];
139}
140
141+ (id)mockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol {
142    return [[[self alloc] initWithName:aName forProtocol:aProtocol] autorelease];
143}
144
145+ (id)nullMockForClass:(Class)aClass {
146    return [[[self alloc] initAsNullMockForClass:aClass] autorelease];
147}
148
149+ (id)nullMockForProtocol:(Protocol *)aProtocol {
150    return [[[self alloc] initAsNullMockForProtocol:aProtocol] autorelease];
151}
152
153+ (id)nullMockWithName:(NSString *)aName forClass:(Class)aClass {
154    return [[[self alloc] initAsNullMockWithName:aName forClass:aClass] autorelease];
155}
156
157+ (id)nullMockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol {
158    return [[[self alloc] initAsNullMockWithName:aName forProtocol:aProtocol] autorelease];
159}
160
161+ (id)partialMockWithName:(NSString *)aName forObject:(id)object {
162    return [[[self alloc] initAsPartialMockWithName:aName forObject:object] autorelease];
163}
164
165+ (id)partialMockForObject:(id)object {
166    return [[[self alloc] initAsPartialMockForObject:object] autorelease];
167}
168
169- (void)dealloc {
170    [mockedObject release];
171    [mockName release];
172    [stubs release];
173    [expectedMessagePatterns release];
174    [messageSpies release];
175    [super dealloc];
176}
177
178#pragma mark -
179#pragma mark Properties
180
181@synthesize isPartialMock;
182@synthesize isNullMock;
183@synthesize mockName;
184@synthesize mockedObject;
185@synthesize mockedClass;
186@synthesize mockedProtocol;
187@synthesize stubs;
188@synthesize expectedMessagePatterns;
189@synthesize messageSpies;
190
191#pragma mark -
192#pragma mark Getting Transitive Closure For Mocked Protocols
193
194- (NSSet *)mockedProtocolTransitiveClosureSet {
195    if (self.mockedProtocol == nil)
196        return nil;
197
198    NSMutableSet *protocolSet = [NSMutableSet set];
199    NSMutableArray *protocolQueue = [NSMutableArray array];
200    [protocolQueue addObject:self.mockedProtocol];
201
202    do {
203        Protocol *protocol = [protocolQueue lastObject];
204        [protocolSet addObject:protocol];
205        [protocolQueue removeLastObject];
206
207        unsigned int count = 0;
208        Protocol **protocols = (Protocol **)protocol_copyProtocolList(protocol, &count);
209
210        if (count == 0)
211            continue;
212
213        for (unsigned int i = 0; i < count; ++i)
214            [protocolQueue addObject:protocols[i]];
215
216        free(protocols);
217    } while ([protocolQueue count] != 0);
218
219    return protocolSet;
220}
221
222#pragma mark -
223#pragma mark Stubbing Methods
224
225- (void)removeStubWithMessagePattern:(KWMessagePattern *)messagePattern {
226    KWStub *stub = [self currentStubWithMessagePattern:messagePattern];
227    if (stub) {
228        [self.stubs removeObject:stub];
229    }
230}
231
232- (KWStub *)currentStubWithMessagePattern:(KWMessagePattern *)messagePattern {
233    NSUInteger stubCount = [self.stubs count];
234    
235    for (NSUInteger i = 0; i < stubCount; ++i) {
236        KWStub *stub = (self.stubs)[i];
237        
238        if ([stub.messagePattern isEqualToMessagePattern:messagePattern]) {
239            return stub;
240        }
241    }
242    return nil;
243}
244
245- (void)stub:(SEL)aSelector {
246    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:aSelector];
247    [self stubMessagePattern:messagePattern andReturn:nil];
248}
249
250- (void)stub:(SEL)aSelector withBlock:(id (^)(NSArray *params))block {
251    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:aSelector];
252    [self stubMessagePattern:messagePattern withBlock:block];
253}
254
255- (void)stub:(SEL)aSelector withArguments:(id)firstArgument, ... {
256    va_list argumentList;
257    va_start(argumentList, firstArgument);
258    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:aSelector firstArgumentFilter:firstArgument argumentList:argumentList];
259    [self stubMessagePattern:messagePattern andReturn:nil];
260}
261
262- (void)stub:(SEL)aSelector andReturn:(id)aValue {
263    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:aSelector];
264    [self stubMessagePattern:messagePattern andReturn:aValue];
265}
266
267- (void)stub:(SEL)aSelector andReturn:(id)aValue withArguments:(id)firstArgument, ... {
268    va_list argumentList;
269    va_start(argumentList, firstArgument);
270    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:aSelector firstArgumentFilter:firstArgument argumentList:argumentList];
271    [self stubMessagePattern:messagePattern andReturn:aValue];
272}
273
274- (id)stub {
275    NSDictionary *userInfo = @{ExpectOrStubTagKey: StubTag};
276    return [KWInvocationCapturer invocationCapturerWithDelegate:self userInfo:userInfo];
277}
278
279- (id)stubAndReturn:(id)aValue {
280    NSDictionary *userInfo = @{ExpectOrStubTagKey: StubTag,
281                                                                        StubValueKey: aValue};
282    return [KWInvocationCapturer invocationCapturerWithDelegate:self userInfo:userInfo];
283}
284
285- (id)stubAndReturn:(id)aValue times:(id)times afterThatReturn:(id)aSecondValue {
286    NSDictionary *userInfo = @{ExpectOrStubTagKey: StubTag, StubValueKey: aValue, ChangeStubValueAfterTimesKey: times, StubSecondValueKey: aSecondValue};
287    return [KWInvocationCapturer invocationCapturerWithDelegate:self userInfo:userInfo];
288}
289
290- (void)stubMessagePattern:(KWMessagePattern *)aMessagePattern andReturn:(id)aValue {
291    [self stubMessagePattern:aMessagePattern andReturn:aValue overrideExisting:YES];
292}
293
294- (void)stubMessagePattern:(KWMessagePattern *)aMessagePattern andReturn:(id)aValue overrideExisting:(BOOL)overrideExisting {
295    [self expectMessagePattern:aMessagePattern];
296    KWStub *existingStub = [self currentStubWithMessagePattern:aMessagePattern];
297    if (existingStub) {
298        if (overrideExisting) {
299            [self.stubs removeObject:existingStub];
300        } else {
301            return;
302        }
303    }
304    KWStub *stub = [KWStub stubWithMessagePattern:aMessagePattern value:aValue];
305    [self.stubs addObject:stub];
306}
307
308- (void)stubMessagePattern:(KWMessagePattern *)aMessagePattern withBlock:(id (^)(NSArray *params))block {
309    [self expectMessagePattern:aMessagePattern];
310    [self removeStubWithMessagePattern:aMessagePattern];
311    KWStub *stub = [KWStub stubWithMessagePattern:aMessagePattern block:block];
312    [self.stubs addObject:stub];
313}
314
315- (void)stubMessagePattern:(KWMessagePattern *)aMessagePattern andReturn:(id)aValue times:(id)times afterThatReturn:(id)aSecondValue {   
316    [self expectMessagePattern:aMessagePattern];
317    [self removeStubWithMessagePattern:aMessagePattern];
318    KWStub *stub = [KWStub stubWithMessagePattern:aMessagePattern value:aValue times:times afterThatReturn:aSecondValue];
319    [self.stubs addObject:stub];
320}
321
322- (void)clearStubs {
323    [self.stubs removeAllObjects];
324}
325
326#pragma mark -
327#pragma mark Spying on Messages
328
329- (void)addMessageSpy:(id<KWMessageSpying>)aSpy forMessagePattern:(KWMessagePattern *)aMessagePattern {
330    [self expectMessagePattern:aMessagePattern];
331    NSMutableArray *messagePatternSpies = (self.messageSpies)[aMessagePattern];
332
333    if (messagePatternSpies == nil) {
334        messagePatternSpies = [[NSMutableArray alloc] init];
335        (self.messageSpies)[aMessagePattern] = messagePatternSpies;
336        [messagePatternSpies release];
337    }
338    NSValue *spyWrapper = [NSValue valueWithNonretainedObject:aSpy];
339
340    if (![messagePatternSpies containsObject:spyWrapper])
341        [messagePatternSpies addObject:spyWrapper];
342}
343
344- (void)removeMessageSpy:(id<KWMessageSpying>)aSpy forMessagePattern:(KWMessagePattern *)aMessagePattern {
345    NSValue *spyWrapper = [NSValue valueWithNonretainedObject:aSpy];
346    NSMutableArray *messagePatternSpies = (self.messageSpies)[aMessagePattern];
347    [messagePatternSpies removeObject:spyWrapper];
348}
349
350#pragma mark -
351#pragma mark Expecting Message Patterns
352
353- (void)expect:(SEL)aSelector {
354    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:aSelector];
355    [self expectMessagePattern:messagePattern];
356}
357
358- (void)expect:(SEL)aSelector withArguments:(id)firstArgument, ... {
359    va_list argumentList;
360    va_start(argumentList, firstArgument);
361    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:aSelector firstArgumentFilter:firstArgument argumentList:argumentList];
362    [self expectMessagePattern:messagePattern];
363}
364
365- (id)expect {
366    NSDictionary *userInfo = @{ExpectOrStubTagKey: ExpectTag};
367    return [KWInvocationCapturer invocationCapturerWithDelegate:self userInfo:userInfo];
368}
369
370- (void)expectMessagePattern:(KWMessagePattern *)aMessagePattern {
371    if (![self.expectedMessagePatterns containsObject:aMessagePattern])
372        [self.expectedMessagePatterns addObject:aMessagePattern];
373}
374
375#pragma mark -
376#pragma mark Capturing Invocations
377
378- (NSMethodSignature *)invocationCapturer:(KWInvocationCapturer *)anInvocationCapturer methodSignatureForSelector:(SEL)aSelector {
379    return [self methodSignatureForSelector:aSelector];
380}
381
382- (void)invocationCapturer:(KWInvocationCapturer *)anInvocationCapturer didCaptureInvocation:(NSInvocation *)anInvocation {
383    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternFromInvocation:anInvocation];
384    NSString *tag = (anInvocationCapturer.userInfo)[ExpectOrStubTagKey];
385    if ([tag isEqualToString:StubTag]) {
386        id value = (anInvocationCapturer.userInfo)[StubValueKey];
387        if (!(anInvocationCapturer.userInfo)[StubSecondValueKey]) {
388            [self stubMessagePattern:messagePattern andReturn:value];
389        } else {
390            id times = (anInvocationCapturer.userInfo)[ChangeStubValueAfterTimesKey];
391            id secondValue = (anInvocationCapturer.userInfo)[StubSecondValueKey];
392            [self stubMessagePattern:messagePattern andReturn:value times:times afterThatReturn:secondValue];
393        }
394    } else {
395        [self expectMessagePattern:messagePattern];
396    }
397}
398
399#pragma mark -
400#pragma mark Handling Invocations
401
402- (NSString *)namePhrase {
403    if (self.mockName == nil)
404        return @"mock";
405    else
406        return [NSString stringWithFormat:@"mock \"%@\"", self.mockName];
407}
408
409- (BOOL)processReceivedInvocation:(NSInvocation *)invocation {
410    for (KWMessagePattern *messagePattern in self.messageSpies) {
411        if ([messagePattern matchesInvocation:invocation]) {
412            NSArray *spies = (self.messageSpies)[messagePattern];
413
414            for (NSValue *spyWrapper in spies) {
415                id spy = [spyWrapper nonretainedObjectValue];
416                [spy object:self didReceiveInvocation:invocation];
417            }
418        }
419    }
420
421    for (KWStub *stub in self.stubs) {
422        if ([stub processInvocation:invocation])
423            return YES;
424    }
425
426    return NO;
427}
428
429- (NSMethodSignature *)mockedProtocolMethodSignatureForSelector:(SEL)aSelector {
430    NSSet *protocols = [self mockedProtocolTransitiveClosureSet];
431
432    for (Protocol *protocol in protocols) {
433        struct objc_method_description description = protocol_getMethodDescription(protocol, aSelector, NO, YES);
434
435        if (description.types == nil)
436            description = protocol_getMethodDescription(protocol, aSelector, YES, YES);
437
438        if (description.types != nil)
439            return [NSMethodSignature signatureWithObjCTypes:description.types];
440    }
441
442    return nil;
443}
444
445- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
446    NSMethodSignature *methodSignature = [self.mockedClass instanceMethodSignatureForSelector:aSelector];
447
448    if (methodSignature != nil)
449        return methodSignature;
450
451    methodSignature = [self mockedProtocolMethodSignatureForSelector:aSelector];
452
453    if (methodSignature != nil)
454        return methodSignature;
455
456    NSString *encoding = KWEncodingForVoidMethod();
457    return [NSMethodSignature signatureWithObjCTypes:[encoding UTF8String]];
458}
459
460- (void)forwardInvocation:(NSInvocation *)anInvocation {
461#if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
462    @try {
463#endif // #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
464
465    if ([self processReceivedInvocation:anInvocation])
466        return;
467
468    if (isPartialMock)
469        [anInvocation invokeWithTarget:self.mockedObject];
470
471    if (self.isNullMock)
472        return;
473
474    for (KWMessagePattern *expectedMessagePattern in self.expectedMessagePatterns) {
475        if ([expectedMessagePattern matchesInvocation:anInvocation])
476            return;
477    }
478
479    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternFromInvocation:anInvocation];
480    [NSException raise:@"KWMockException" format:@"%@ received unexpected message -%@",
481                                                 [self namePhrase],
482                                                 [messagePattern stringValue]];
483
484#if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
485    } @catch (NSException *exception) {
486        KWSetExceptionFromAcrossInvocationBoundary(exception);
487    }
488#endif // #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
489}
490
491#pragma mark -
492#pragma mark Testing Objects
493
494- (BOOL)mockedClassHasAncestorClass:(Class)aClass {
495    Class currentClass = self.mockedClass;
496
497    while (currentClass != nil) {
498        if (currentClass == aClass)
499            return YES;
500
501        currentClass = [currentClass superclass];
502    }
503
504    return NO;
505}
506
507- (BOOL)mockedClassRespondsToSelector:(SEL)aSelector {
508    return [self.mockedClass instancesRespondToSelector:aSelector];
509}
510
511- (BOOL)mockedClassConformsToProtocol:(Protocol *)aProtocol {
512    return [self.mockedClass conformsToProtocol:aProtocol];
513}
514
515- (BOOL)mockedProtocolRespondsToSelector:(SEL)aSelector {
516    NSSet *protocols = [self mockedProtocolTransitiveClosureSet];
517
518    for (Protocol *protocol in protocols) {
519        struct objc_method_description description = protocol_getMethodDescription(protocol, aSelector, NO, YES);
520
521        if (description.types == nil)
522            description = protocol_getMethodDescription(protocol, aSelector, YES, YES);
523
524        if (description.types != nil)
525            return YES;
526    }
527
528    return NO;
529}
530
531- (BOOL)mockedProtocolConformsToProtocol:(Protocol *)aProtocol {
532    if (self.mockedProtocol == nil)
533        return NO;
534
535    return protocol_isEqual(self.mockedProtocol, aProtocol) || protocol_conformsToProtocol(self.mockedProtocol, aProtocol);
536}
537
538- (BOOL)isKindOfClass:(Class)aClass {
539    return [self mockedClassHasAncestorClass:aClass] || [super isKindOfClass:aClass];
540}
541
542- (BOOL)isMemberOfClass:(Class)aClass {
543    return self.mockedClass == aClass || [super isMemberOfClass:aClass];
544}
545
546- (BOOL)respondsToSelector:(SEL)aSelector {
547    return [self mockedClassRespondsToSelector:aSelector] ||
548           [self mockedProtocolRespondsToSelector:aSelector] ||
549           [super respondsToSelector:aSelector];
550}
551
552- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
553    return [self mockedClassConformsToProtocol:aProtocol] ||
554           [self mockedProtocolConformsToProtocol:aProtocol] ||
555           [super conformsToProtocol:aProtocol];
556}
557
558#pragma mark -
559#pragma mark Whitelisted NSObject Methods
560
561- (BOOL)isEqual:(id)anObject {
562    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:_cmd];
563    [self expectMessagePattern:messagePattern];
564    NSInvocation *invocation = [NSInvocation invocationWithTarget:self selector:_cmd messageArguments:&anObject];
565
566    if ([self processReceivedInvocation:invocation]) {
567        BOOL result = NO;
568        [invocation getReturnValue:&result];
569        return result;
570    } else {
571        return [super isEqual:anObject];
572    }
573}
574
575- (NSUInteger)hash {
576    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:_cmd];
577    [self expectMessagePattern:messagePattern];
578    NSInvocation *invocation = [NSInvocation invocationWithTarget:self selector:_cmd];
579
580    if ([self processReceivedInvocation:invocation]) {
581        NSUInteger result = 0;
582        [invocation getReturnValue:&result];
583        return result;
584    } else {
585        return [super hash];
586    }
587}
588
589- (NSString *)description {
590    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:_cmd];
591    [self expectMessagePattern:messagePattern];
592    NSInvocation *invocation = [NSInvocation invocationWithTarget:self selector:_cmd];
593
594    if ([self processReceivedInvocation:invocation]) {
595        NSString *result = nil;
596        [invocation getReturnValue:&result];
597        return result;
598    } else {
599        return [super description];
600    }
601}
602
603- (id)copy {
604    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:_cmd];
605    [self expectMessagePattern:messagePattern];
606    NSInvocation *invocation = [NSInvocation invocationWithTarget:self selector:_cmd];
607
608    if ([self processReceivedInvocation:invocation]) {
609        id result = nil;
610        [invocation getReturnValue:&result];
611        return result;
612    } else {
613        return [super copy];
614    }
615}
616
617- (id)mutableCopy {
618    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:_cmd];
619    [self expectMessagePattern:messagePattern];
620    NSInvocation *invocation = [NSInvocation invocationWithTarget:self selector:_cmd];
621
622    if ([self processReceivedInvocation:invocation]) {
623        id result = nil;
624        [invocation getReturnValue:&result];
625        return result;
626    } else {
627        return [super mutableCopy];
628    }
629}
630
631#pragma mark -
632#pragma mark Key-Value Coding Support
633
634static id valueForKeyImplementation(id self, SEL _cmd, id key) {
635    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:_cmd];
636    [self expectMessagePattern:messagePattern];
637    NSInvocation *invocation = [NSInvocation invocationWithTarget:self selector:_cmd messageArguments:&key];
638    
639    if ([self processReceivedInvocation:invocation]) {
640        id result = nil;
641        [invocation getReturnValue:&result];
642        return result;
643    } else {
644        return nil;
645    }
646}
647
648- (id)valueForKey:(NSString *)key {
649    return valueForKeyImplementation(self, _cmd, key);
650}
651
652- (id)valueForKeyPath:(NSString *)keyPath {
653    return valueForKeyImplementation(self, _cmd, keyPath);
654}
655
656static void setValueForKeyImplementation(id self, SEL _cmd, id a, id b) {
657    KWMessagePattern *messagePattern = [KWMessagePattern messagePatternWithSelector:_cmd];
658    [self expectMessagePattern:messagePattern];
659    NSInvocation *invocation = [NSInvocation invocationWithTarget:self selector:_cmd messageArguments:&a, &b];
660    
661    [self processReceivedInvocation:invocation];
662}
663
664- (void)setValue:(id)value forKey:(NSString *)key {
665    setValueForKeyImplementation(self, _cmd, value, key);
666}
667
668- (void)setValue:(id)value forKeyPath:(NSString *)keyPath {
669    setValueForKeyImplementation(self, _cmd, value, keyPath);
670}
671
672@end